Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python generics and subclasses

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
like image 742
brunns Avatar asked Aug 16 '19 08:08

brunns


People also ask

What are Generics in Python?

Generic is a programming library for Python that provides tools for generic programming. By now, there is only one feature – multiple dispatch.

Does Python have generic classes?

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.

What is Typevar in Python?

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.

Is Generics a data type in Python?

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.


2 Answers

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

like image 70
Michael0x2a Avatar answered Oct 30 '22 10:10

Michael0x2a


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"
like image 39
Victor Ruiz Avatar answered Oct 30 '22 12:10

Victor Ruiz