typing
typing copied to clipboard
How should we annotate functions that forward to their superclass?
Consider this real code:
from typing import SuperKwargs
class InferenceManager(Generic[T]):
@override
def __init__(self,
*,
default_trajectory: T,
progress_manager: None | ProgressManager,
wandb_run: None | Run
) -> None:
super().__init__()
self._progress_manager = progress_manager
self._results: list[T] = []
self._trajectory = default_trajectory
self._wandb_run = wandb_run
class TrainingInferenceManager(InferenceManager[RLTrainingResult]):
def __init__(self, training_result: TrainingResult, **kwargs: SuperKwargs):
self.training_result = training_result
super().__init__(**kwargs)
If we want full annotations for TrainingInferenceManager.__init__, we currently need to duplicate all of the superclass's parameters. I suggest adding typing.SuperKwargs that stands in place of them.
(This could be made more complicated by allowing the child class to synthesize some of the parameters.)
Related: https://github.com/python/mypy/issues/8769
If you're using a smart editor or language server, it should take little or no effort to duplicate the signature when overriding a method. For example, with pylance installed in VS Code, you can type def __init__, then hit tab, and the entire signature of the base class will be copied along with a call to super().__init__(). I realize that's not quite what you're asking for here, but I mention it in case you weren't aware that this functionality existed.
Thanks @erictraut, that's a good point. I use NeoVim, which afaik doesn't have this functionality. I'm sure you're aware, but just for any other readers, the limitations of copying the base class signature include:
- there may be more than one base class,
- the base classes can change, which leads to churn in all derived class signatures, and
- as in the example above, the base class is generic, but the derived class isn't, so the editor would have to do the generic type substitution.
@NeilGirdhar Chiming in: I use NeoVim too, and with these plugins, I do get the def __init__ tab completion on a child class
use {
"williamboman/mason.nvim",
"williamboman/mason-lspconfig.nvim",
"neovim/nvim-lspconfig",
}
Using mason.nvim I installed pyright with LspInstall pyright. Mason plugs pyright into NeoVim's native LSP framework.
On this topic, I think there is a need for a way to forward parameters annotations in general, not just for the purpose of inheritance. For example, I often write pure helper functions that are called inside a method that take the same parameters as the method.
If you're using a smart editor or language server, it should take little or no effort to duplicate the signature when overriding a method
A plain-text copy of another library's source code is a flaky error-prone thing to maintain, even if it is possible to generate one.
You might end up having to import a big pile of weird internal details that the upstream library uses in their type annotations that might break at any time, and are otherwise irrelevant to your application.
For example, consider the case of writing a type stub for geopandas.GeoSeries, which inherits from pandas.Series. The problem is that pandas.Series.__new__ has 6 parameters and 4 overloads, and several of those parameters are themselves complicated internal-only things that have no stability or interface guarantees.
Similarly, imagine a case where I'm writing my own subclass of Series, and all I want to do is define a new method and add an extra optional parameter to __init__. Ideally I'd be able to forward the types of *args, **kwargs to the parent class and not have to maintain my own copy of the entire signature.
I think the above cases are common enough and painful enough that something like SuperKwargs would be very much appreciated by many people.