Reputation: 90
When annotating a function parameter with a bound TypeVar
, giving it a default value results in the parameter having a union type between the TypeVar
and the default value type, even though the default value is of the TypeVar
type.
Example:
class A:
pass
class B(A):
pass
Instance = TypeVar("Instance", bound=A)
def get_instance(cls: type[Instance] = A) -> Instance:
return cls()
Running mypy yields the following error: error: Incompatible default for argument "cls" (default has type "Type[A]", argument has type "Type[Instance]")
.
reveal_type
is correct in both cases:
instance_a = get_instance(cls=A)
reveal_type(instance_a) # note: Revealed type is "A"
instance_b = get_instance(cls=B)
reveal_type(instance_b) # note: Revealed type is "B"
How do I correctly annotate get_instance
so that I can keep the default argument?
Upvotes: 4
Views: 807
Reputation: 7913
It is a very old mypy
issue.
The general solution is using overload
's, it can be applied in your case:
from typing import TypeVar, overload
class A:
pass
class B(A):
pass
_Instance = TypeVar("_Instance", bound=A)
@overload
def get_instance() -> A: ...
@overload
def get_instance(cls: type[_Instance]) -> _Instance: ...
def get_instance(cls: type[A] = A) -> A:
return cls()
instance_a = get_instance(cls=A)
reveal_type(instance_a) # note: Revealed type is "A"
instance_b = get_instance(cls=B)
reveal_type(instance_b) # note: Revealed type is "B"
For type checking purposes, we define two overloads. Implementation signature is checked only within the function, external callers see only the overloads. Here we just ignore specific relationship between cls
type and return type while checking body, however, you can consider other solutions from the linked issue.
Upvotes: 5