-
Notifications
You must be signed in to change notification settings - Fork 288
-
Hello o/
I have a typing question / suggestion. I wasn't sure where to put this exactly, so if this isn't the right place, please let me know. For context, I normally use Python 3.8.10 via VSCode with Pylance for linting and MyPy for type checking.
While I was working on my Python project, I ran into a situation where I needed to create a subclass of argparse.ArgumentParser, to override some methods on it. Additionally, I needed an extra attribute to be stored in the parser, called self._message. Sounded easy enough, I just needed to override __init__ with a super call and set the attribute to some initial value.
Since I'm not changing the __init__'s signature, the easiest way is to just use *args and **kwargs to forward everything to the super call:
class MyParser(argparse.ArgumentParser): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._message = ...
This creates a big issue though. By doing this, my overridden __init__ just lost all of it's typing information. argparse.ArgumentParser happens to take in quite a bit of parameters of different kinds, and I just robbed myself of any linting information that'd be useful when I'd get to making an instance of it.
I initially tried to use the new ParamSpec feature, to try and see if it'd be able to help here, but after trying a couple of different things and rereading PEP 612, it turned out that this "signature forwarding" isn't currently supported in any way.
The only way to go about this right now, is to "manually" copy the entire signature. Here's how it looks like:
class MyParser(argparse.ArgumentParser): def __init__(self, prog: str | None = None , usage: str | None = None, description: str | None = None, epilog: str | None = None, parents: Sequence[ArgumentParser] = [], formatter_class: _FormatterClass = argparse.HelpFormatter, prefix_chars: str = '-', fromfile_prefix_chars: str | None = None, argument_default: Any = None, conflict_handler: str = 'error', add_help: bool = True, allow_abbrev: bool = True) -> None: super().__init__(prog, usage, description, epilog, parents, formatter_class, prefix_chars, fromfile_prefix_chars, argument_default, conflict_handler, add_help, allow_abbrev) self._message = ...
There's several problems with this though:
- It quite clunky, especially for long signatures like this one.
- All of the special types used in the signature have to be imported into the project (such as
_FormatterClass). This doesn't look so bad here, but for exampletkinterhas lots of them that can be used in the signature. - The signature has to be kept up-to-date with whatever base class is subclassed. This is already an issue with base Python where the signature can change (like in this case where an
exit_on_error: bool = Trueargument has been added in Python 3.9), but it also affects any 3rd party library that may adjust its signature as well.
So, to summarise, it would be great to have an explicit way to specify "passthrough" signature, or in any other way preserve the typing information, when a method just passes them to a super call. Something like:
P = ParamSpec("P") class MyParser(argparse.ArgumentParser): # ParamSpec is used to forward signature here # It's scope is defined within the method only def __init__(self, *args: P.args, **kwargs: P.kwargs): super().__init__(*args, **kwargs) self._message = ...
I'm assuming this isn't currently supported? And if not, isn't this something that would be quite helpful in general development that involves typing?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 4 comments 1 reply
-
As you surmise, this currently isn't supported. ParamSpec is similar but doesn't quite fit here. I feel like I might have seen this discussed before, but can't find anything on the typing issue tracker.
Beta Was this translation helpful? Give feedback.
All reactions
-
I wasn't able to find anything either, which is why I created this discussion post. I think this idea is reasonably cool to have something for in the typing ecosystem. Whether or not ParamSpec is to-be-used to implement this or not, isn't really up for me to decide - it's just something that I felt fit the closest here, to try and get the point across.
I saw someone suggest using functools.wraps as it copies __annotations__, but I'm pretty sure this doesn't really work statically, and a quick test confirms this (unless I did something wrong). So, well... I'm leaving it as an idea out there, as something that'd be useful to have.
Beta Was this translation helpful? Give feedback.
All reactions
-
Good point, it might work with python/typeshed#6670 applied.
Beta Was this translation helpful? Give feedback.
All reactions
-
For now, you can make your own @copy_type decorator, as I suggested here: #769 (comment)
It has downsides, but it works.
Beta Was this translation helpful? Give feedback.
All reactions
-
This was also brought up on discuss: add bound to paramspec and recently in python/mypy#17119
Beta Was this translation helpful? Give feedback.