Mypy errors in Generic class

Question:

I slightly modified the documented mypy generic example but got an error:

from typing import Generic, TypeVar, Union

T = TypeVar("T", bound=Union[int, float])

class Person(Generic[T]):

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

    def get_yearly_bonus(self, amount: T) -> None:
        self.salary += amount

    def __str__(self) -> str:
        return f"Person(salary={self.salary})"

p = Person[int](9)
p.get_yearly_bonus(6)
print(p)

It seems mypy fails to recognize the fact that self.salary and amount must
be of the same type.

$ mypy test.py 
test.py:11: error: Incompatible types in assignment (expression has type "float", variable has type "T")
test.py:11: error: Unsupported operand types for + (likely involving Union)
Found 2 errors in 1 file (checked 1 source file)
Asked By: OrenIshShalom

||

Answers:

The problem isn’t mypy failing to recognize that self.salary and amount share a type. mypy knows they both have type T, whatever T is.

The problem is that T isn’t guaranteed to be either int or float. T can be any subtype of Union[int, float]. It could be int, or float, or any subclass of those classes, like bool, or it could even be Union[int, float]!

For a sum of two objects of type T, mypy cannot assume the sum has type T. The most specific type mypy can deduce for the sum (accounting for mypy’s weird special case that treats int as a subtype of float) is float, which isn’t assignable to self.salary.

If you wanted T to be either int or float, a union bound isn’t the way to do that. The syntax for "either int or float" is

T = TypeVar('T', int, float)

With T specified this way, mypy can type-check Person against both possible types and see that the class is valid both ways.

Answered By: user2357112
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.