如何使用多个 @overloads (ArgumentParser.parse_args) 正确覆盖静态类型的方法

How do I correctly override a statically typed method with multiple @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-statically-typed-methods,但仍有许多问题我不清楚:

必须匹配超类的所有签名。示例:

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 的解决方案。


要了解为什么第一个解决方案不起作用,请考虑

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

虽然很明显 Base.fun returns str 只有 如果 varNoneClass1.fun 的签名不能说相同。因此,似乎需要复制完整的 @overload 集,例如 Class2.fun,除非找到更聪明的解决方案(比较@hussic 的 TypeVar("T", int, str) 示例)——在这种情况下,这可能应该直接合并到对应的package/typeshed中。 Class3.fun 是另一种总能奏效的解决方法。