Search code examples
pythongenericsmypypython-typing

mypy: How to declare the return type of a method returning self in a generic class?


This answer does not seem to work for generics. Mypy complains about "error: Missing type parameters for generic type A" when checking the following code. I have tried using 'A[T]' for the TypeVar but then mypy says "error: Type variable T is unbound." I have also tried using AnyA[T] as return type of get but that produces two error messages, the already known "error: Missing type parameters for generic type A" and the new error message "Type variable AnyA used with arguments".

How do I specify the return type of get correctly?

import typing

T = typing.TypeVar('T')
AnyA = typing.TypeVar('AnyA', bound='A')

class A(typing.Generic[T]):

    def __init__(self, val: T) -> None:
        self.val = val

    def get(self: AnyA) -> AnyA:
        return self

class B(A[T]):
    def is_int(self) -> bool:
        return isinstance(self.val, int)


if __name__ == '__main__':
    b = B(42)
    print(b.get().is_int())

Solution

  • I know of three ways of typing here:

    Declaring an inner self-type

    This approach is described in mypy docs, see Precise typing of alternative constructors.

    class A(typing.Generic[T]):
        _Self = typing.TypeVar('_Self', bound='A[T]')
    
        def __init__(self, val: T) -> None:
            self.val = val
    
        def get(self: _Self) -> _Self:
            return self
    

    Note however, that this is mypy-specific stuff and may not work with other checkers. E.g. pyre doesn't support inner self-types yet.

    Using _typeshed.Self

    This saves the boilerplate of declaring custom types, but requires a somewhat obscure import from typeshed which will fail at runtime. It thus must be wrapped by typing.TYPE_CHECKING:

    from typing import Any, TYPE_CHECKING
    
    if TYPE_CHECKING:
        from _typeshed import Self
    else:
        Self = Any
    
    class A(typing.Generic[T]):
        def __init__(self, val: T) -> None:
            self.val = val
    
        def get(self: Self) -> Self:
            return self
    

    _typeshed.Self was created to be used in custom stubs in the first place, but is suitable for inline typing as well.

    Python 3.11 and upwards: typing.Self

    A recently introduced PEP 673 adds Self to stdlib, so starting from Python 3.11 one will be able to use that:

    from typing import Self
    
    class A(typing.Generic[T]):
        def __init__(self, val: T) -> None:
            self.val = val
    
        def get(self: Self) -> Self:
            return self
    

    This is not supported by mypy yet as of now though, but e.g. by pyright from version 1.1.184.