You have understood Python generics wrong. A Generic, say Generic[T], makes the TypeVar T act as whatever you provide the instance with.
I found this page to be very useful with learning Generics.
Answer from sean-7777 on Stack OverflowYou have understood Python generics wrong. A Generic, say Generic[T], makes the TypeVar T act as whatever you provide the instance with.
I found this page to be very useful with learning Generics.
typing.Generic is specifically for type annotations; Python provides no runtime concept equivalent to either:
- Template classes, or
- Function overloads by parameter count/type
The latter has extremely limited support from functools.singledispatch, but it only handles the type of one argument, and the runtime type-checking is fairly slow.
That said, you don't need a template-based class or a polymorphic inheritance hierarchy here; Python is perfectly happy to call draw on anything that provides a method of that actual name. The code you're writing here can just do:
Copya = A(5)
b = B('Hello')
a.draw()
b.draw()
with no use of G needed.
Specialized Generic typed class plus class inheritance
When to make classes & functions generic in python?
Is it possible to inherit from class with Generic in Python? - Stack Overflow
How to share type variables when inheriting from generic base classes? - Typing - Discussions on Python.org
Videos
I have been generally making my methods generic mostly for the following reasons:
-
I am subclassing an abstract class and want to override a method and narrow the type hinting in the arguments, which would otherwise violate the Liskov substitution principle
-
I am not subclassing/overriding, but would like return values, attributes, etc. of a class to be more narrow than the type hints it is currently bound to, since I may be using that class in many different places with different types.
In particular for the second case, I have realized that there are actually two approaches to tackle this:
-
make the class generic and provide the type arguments for the instance type hints.
-
do not make the class generic, but subclass simply for the purpose of updating the type hints
With projects I am working on, with "context" and "manager" classes, there can be many different attributes and methods with many different types (5+), hence, making the class generic on all of them is too verbose. If I do make a class generic, if any other attributes (containing instances of other classes) return those same generic types, I have to propagate the generic type down the entire chain when I am using composition instead of inheritance. This is something I would like to avoid. If I choose option 2, there would be an explosion of subclasses just to override the type hints.
When should I choose 1 or 2? Is there a better way to do this?
Option 1 example:
from typing import Self, TypeVar, Generic
BazT = TypeVar('BazT')
class Bar(Generic[BazT]):
def method1(self: Self) -> BazT:
...
class Foo(Generic[BazT]):
bar: Bar[BazT]
def method1(self: Self, baz: BazT) -> None:
...
def method2(self: Self) -> Bar[BazT]:
...Option 2 example:
from typing import Self, Any, TypeVar
Baz = Any
class Bar:
def method1(self: Self) -> Baz:
...
class Foo:
bar: Bar
def method1(self: Self, baz: Baz) -> None:
...
def method2(self: Self) -> Bar:
...
BazNarrowed = TypeVar('BazNarrowed', bound=Baz) # (doesn't matter what this is just some more narrow type)
class BarSubclass(Bar):
def method1(self: Self) -> BazNarrowed:
...
class FooSubclass(Foo):
bar: BarSubclass
def method1(self: Self, baz: BazNarrowed) -> None:
...
def method2(self: Self) -> BarSubclass:
...This was a bug in mypy that was fixed in mypy 0.700. As several people in the comments noted, that line of code validates fine in newer versions.
Note that in newer versions of mypy, the code in the question has a different problem:
main.py:8: error: Incompatible types in assignment (expression has type "None", variable has type "EntityId")
main.py:9: error: Incompatible types in assignment (expression has type "None", variable has type "StateType")
But that's outside the scope of the question and up to you to resolve however you'd like.
I know this question is a little old, but just for future reference:
from __future__ import annotations
from typing import NamedTuple, Optional, Type
class UserState(NamedTuple):
name: str
age: int
class Entity:
id: Optional[str]
state: UserState
@classmethod
def from_state(cls: Type[Entity], state: UserState) -> Entity:
entity_from_state: Entity = object.__new__(cls)
entity_from_state.id = None
entity_from_state.state = state
return entity_from_state
def assign_id(self, id: str) -> None:
self.id = id
class User(Entity):
def __init__(self, name: str, age: int) -> None:
self.state = UserState(name=name, age=age)
@property
def name(self) -> str:
return self.state.name
@property
def age(self) -> int:
return self.state.age
def have_birthday(self) -> None:
new_age = self.state.age + 1
self.state = self.state._replace(age=new_age)
# Create first object with constructor
u1 = User(name="Anders", age=47)
# Create second object from state
user_state = UserState(name="Hannes", age=27)
u2 = User.from_state(user_state) # Line 47
print(u1.state)
print(u2.state)