如何正确地用多个@overloads(ArgumentParser.parse_args)重写静态类型的方法



我正在尝试扩展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)

不过,我觉得这只是一个有点太冗长了。

我想知道是否有解决方案可以让我不必重复所有@overloads。


要了解第一个解决方案不起作用的原因,请考虑

"""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)

虽然很明显,如果varNone,则Base.fun仅返回str,但从Class1.fun的签名来看,情况并非如此。因此,似乎需要复制@overload的完整集合,例如在Class2.fun中,除非找到更智能的解决方案(比较@hussic的TypeVar("T", int, str)示例(——在这种情况下,可能应该直接将其合并到相应的包/typeshed中。Class3.fun是另一种在某种程度上总是有效的变通方法。

最新更新