Home > Mobile >  How do I correctly override a statically typed method with multiple @overloads (ArgumentParser.parse
How do I correctly override a statically typed method with multiple @overloads (ArgumentParser.parse

Time:02-11

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:

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 @overloads are coming from typeshed, not from argparse 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 @overloads, 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 @overloads.

  • Related