I'm trying to add type annotations to an existing package, and clearly I'm missing something important. I have an abstract superclass, and subclasses. The superclass should be generic, whereas the subclasses should be for a specific type. Here's a simple example if that I see, and what I'd like to see:
from typing import Generic, TypeVar
T = TypeVar("T")
class A(Generic[T]):
def method(self, arg: T):
...
class B(A[int]):
def method(self, arg):
reveal_locals()
Expected (or at least hoped for):
GenericTest.py:11: note: Revealed local types are:
GenericTest.py:11: note: arg: int
GenericTest.py:11: note: self: Any
Got:
GenericTest.py:11: note: Revealed local types are:
GenericTest.py:11: note: arg: Any
GenericTest.py:11: note: self: Any
Generic is a programming library for Python that provides tools for generic programming. By now, there is only one feature – multiple dispatch.
Generics have allowed us to create a class that can be used with multiple types, and then enforce (through the use of tools such as mypy) that only arguments of the specified type are sent to the methods of the instance. Generics are very powerful and help you to better reason about what you are doing and why.
It is a type variable. Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions.
In Python 3.6 indexing generic types or type aliases results in actual type objects. This means that generic types in type annotations can have a significant runtime cost. This was changed in Python 3.7, and indexing generic types became a cheap operation.
You need to add type annotations to the methods in your subclass. So doing:
from typing import Generic, TypeVar
T = TypeVar("T")
class A(Generic[T]):
def method(self, arg: T) -> None:
...
class B(A[int]):
def method(self, arg: int) -> None:
reveal_locals()
...results in the expected output of:
test.py:11: note: Revealed local types are:
test.py:11: note: arg: builtins.int
test.py:11: note: self: test.B
If a function is not annotated with type hints, mypy will treat that function as being dynamically typed and assume all of its arguments are of type Any
.
If you would like mypy to warn you when you forget type hints like this, run mypy with the --disallow-untyped-defs
command line flag -- and also maybe --disallow-incomplete-defs
for good measure. Or alternatively, run mypy with the --strict
flag, which automatically enables the above two flags (and more).
reveal_locals()
prints the types of the variables inferred by mypy in the scope where the call its placed. And when redefining a method in a subclass, you are also overriding the annotations (Any
is used by default when no parameter annotation is given explicitly)
This could be more clear:
class A(Generic[T]):
def method(self, arg: T):
...
class B(A[int]):
def method(self, arg):
pass
B().method('')
The above code is fine for mypy but the next gives an error:
class C(A[int]):
pass
C().method('')
error: Argument 1 to "method" of "A" has incompatible type "str"; expected "int"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With