我正在尝试扩展argparse.ArgumentParser.parse_args
,但mypy
告诉我做得不对:
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
我得到这些错误:
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
我读过https://mypy.readthedocs.io/en/stable/class_basics.html#overriding-静态类型的方法,但仍然有很多事情我不清楚:
这五个不同的方法签名来自哪里?我只能在这里找到一个相关的实现:https://github.com/python/cpython/blob/f20ca766fe404a20daea29230f161a0eb71bb489/Lib/argparse.py#L1843
我必须匹配这些签名中的一个,还是所有(使用某种超集(?
为什么
namespace
对于所有签名都不是可选的,尽管它显然在上面的实现中?什么是
_N
?
超类的所有签名都必须匹配。示例:
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)
如果你确定你的签名,你可以让mypy忽略type: ignore[override]
的覆盖错误,只忽略覆盖错误:
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]
另一种过载解决方案:
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)
TypeVar
的另一个在这种简单的情况下工作:
T = TypeVar("T", int, str)
class B(A):
def f(self, a: T) -> T:
return super().f(a)
注意:您可以使用ignore[error]
忽略每一个Mypy错误。要知道Mypy的错误,您应该使用--show-error-codes
启动Mypy。
学习后
- 五个
@overload
来自typeshed
而不是来自argparse
本身 _N
是允许多次使用(未知(变量类型(例如依赖于输入类型的输出类型(的占位符- 可选参数在每个
@overload
中不必是可选的,以及 - 仅仅匹配所有重载的某个超集是不够的,而是需要复制最小的约束集(谢谢,@hussic(
我仍然希望以下作品:
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)
但当然不是(见下文(。
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]
工作原理是:
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)
不过,我觉得这只是一个小有点太冗长了。
我想知道是否有解决方案可以让我不必重复所有@overload
s。
要了解第一个解决方案不起作用的原因,请考虑
"""Demonstrate bug."""
from typing import Any, TypeVar, overload
_N = TypeVar("_N", int, float)
class Base:
"""Base class."""
@overload
def fun(self, var: None) -> str:
...
@overload
def fun(self, var: _N) -> _N:
...
def fun(self, var: _N = None) -> _N | str:
"""Return var or "NaN"."""
if var is None:
return "NaN"
return var
class Class1(Base):
"""My class."""
def fun(self, var: _N = None) -> _N | str:
"""Return var or "None"."""
return super().fun(var)
class Class2(Base):
"""My class."""
@overload
def fun(self, var: None) -> str:
...
@overload
def fun(self, var: _N) -> _N:
...
def fun(self, var: _N = None) -> _N | str:
"""Return var or "None"."""
return super().fun(var)
class Class3(Base):
"""My class."""
def fun(self, var: Any = None) -> Any:
"""Return var or "None"."""
return super().fun(var)
虽然很明显,如果var
是None
,则Base.fun
仅返回str
,但从Class1.fun
的签名来看,情况并非如此。因此,似乎需要复制@overload
的完整集合,例如在Class2.fun
中,除非找到更智能的解决方案(比较@hussic的TypeVar("T", int, str)
示例(——在这种情况下,可能应该直接将其合并到相应的包/typeshed
中。Class3.fun
是另一种在某种程度上总是有效的变通方法。