I am trying to extend argparse.ArgumentParser.parse_args
, but mypy
tells me I am doing it wrong:
from argparse import ArgumentParser, Namespace
from typing import Sequence
class MyArgumentParser(ArgumentParser):
def parse_args(
self,
args: Sequence[str] = None,
namespace: Namespace = None,
) -> Namespace:
parsed_args = super().parse_args(args, namespace)
# process parsed_args
return parsed_args
I am getting these errors:
Signature of "parse_args" incompatible with supertype "ArgumentParser"
Superclass:
@overload
def parse_args(self, args: Optional[Sequence[str]] = ...) -> Namespace
@overload
def parse_args(self, args: Optional[Sequence[str]], namespace: None) -> Namespace
@overload
def [_N] parse_args(self, args: Optional[Sequence[str]], namespace: _N) -> _N
@overload
def parse_args(self, *, namespace: None) -> Namespace
@overload
def [_N] parse_args(self, *, namespace: _N) -> _N
Subclass:
def parse_args(self, args: Optional[Sequence[str]] = ..., namespace: Optional[Namespace] = ...) -> Namespace
I have read https://mypy.readthedocs.io/en/stable/class_basics.html#overriding-statically-typed-methods, but still, a number of things are unclear to me:
Where are these five different method signatures coming from? I can only find a single relevant implementation, here: https://github.com/python/cpython/blob/f20ca766fe404a20daea29230f161a0eb71bb489/Lib/argparse.py#L1843
Do I have to match one of these signatures, or all of them (using some kind of superset)?
Why is
namespace
not optional for all of the signatures, although it clearly is in the above implementation?What is
_N
?
CodePudding user response:
All signatures of superclass must be matched. Example:
class A:
@overload
def f(self, a: int) -> int: ...
@overload
def f(self, a: str) -> str:...
def f(self, a) -> str | int:
return a
class B(A):
# Mypy: Signature of "f" incompatible with supertype "A"
# Superclass:
# @overload def f(self, a: int) -> int
# @overload def f(self, a: str) -> str
# Subclass:
# def f(self, a: int) -> int
def f(self, a: int) -> int:
return super().f(a)
If you are sure about your signature you can ask mypy to ignore the
override error with type: ignore[override]
which ignore only the override error:
class C(A):
def f(self, a: int) -> int: # type: ignore[override]
return super().f(a)
C().f(3) # Mypy: OK
C().f('3') # Mypy: Argument 1 to "f" of "C" has incompatible type
# "str"; expected "int" [arg-type]
Another solution with overload:
class C(A):
@overload
def f(self, a: int) -> int: ...
@overload
def f(self, a: str) -> str:...
def f(self, a: int | str) -> int | str:
return super().f(a)
Another one with TypeVar
which works in this simple case:
T = TypeVar("T", int, str)
class B(A):
def f(self, a: T) -> T:
return super().f(a)
Note: You can ignore every single Mypy error with ignore[error]
.
To know the error of Mypy you should launch mypy with --show-error-codes
.
CodePudding user response:
After learning
- that the five
@overload
s are coming fromtypeshed
, not fromargparse
itself, - that
_N
is a placeholder allowing multiple use of an (unknown) variable type multiple times (e.g., in inputs and outputs), - that an optional argument does not have to be optional in each of the
@overload
s, and - that it is sufficient to match a superset of all overloads (thanks, @hussic),
I had expected that the following works:
from argparse import ArgumentParser, Namespace
from typing import Sequence, TypeVar
_N = TypeVar("_N")
class MyArgumentParser(ArgumentParser):
def parse_args(
self, args: Sequence[str] = None, namespace: _N = None
) -> _N | Namespace:
return super().parse_args(args, namespace)
but it does not:
Signature of "parse_args" incompatible with supertype "ArgumentParser"
Superclass:
@overload
def parse_args(self, args: Optional[Sequence[str]] = ...) -> Namespace
@overload
def parse_args(self, args: Optional[Sequence[str]], namespace: None) -> Namespace
@overload
def [_N] parse_args(self, args: Optional[Sequence[str]], namespace: _N) -> _N
@overload
def parse_args(self, *, namespace: None) -> Namespace
@overload
def [_N] parse_args(self, *, namespace: _N) -> _N
Subclass:
def [_N] parse_args(self, args: Optional[Sequence[str]] = ..., namespace: Optional[_N] = ...) -> Union[_N, Namespace]
What does work is this:
from argparse import ArgumentParser, Namespace
from typing import Sequence, TypeVar, overload
_N = TypeVar("_N")
class MyArgumentParser(ArgumentParser):
# https://github.com/python/typeshed/blob/494481a0/stdlib/argparse.pyi#L128-L137
@overload
def parse_args(self, args: Sequence[str] | None = ...) -> Namespace:
...
@overload
def parse_args(self, args: Sequence[str] | None, namespace: None) -> Namespace: # type: ignore[misc]
...
@overload
def parse_args(self, args: Sequence[str] | None, namespace: _N) -> _N:
...
@overload
def parse_args(self, *, namespace: None) -> Namespace: # type: ignore[misc]
...
@overload
def parse_args(self, *, namespace: _N) -> _N:
...
def parse_args(
self, args: Sequence[str] = None, namespace: _N = None
) -> _N | Namespace:
return super().parse_args(args, namespace)
which I find just a tiny bit too verbose, though.
I wonder if there are solutions where I don't have to repeat all @overload
s.