I'm doing some experiments with typing in Python 3.6 and mypy. I want to design an entity class that can be instantiated in two ways:
- By the use of an ordinary initializer (
p = Person(name='Hannes', age=27)
) - Statically from a state object (
p = Person.from_state(person_state)
).
The Entity
class, from which Person
derives, has the state class as a generic parameter. However, when validating the code with mypy, I receive an error that Person.from_state
doesn't pick up the state type from the class it inherits from:
untitled2.py:47: error: Argument 1 to "from_state" of "Entity" has incompatible type "UserState"; expected "StateType"
I thought that by inheriting from Entity[UserState]
, StateType
would be bound to UserState
and the method signatures in the child classes would update accordingly.
This is the full code. I have marked the line where I suspect I'm doing things wrong with ?????
. Line 47 is almost at the bottom and marked in the code.
from typing import TypeVar, Generic, NamedTuple, List, NewType
EntityId = NewType('EntityId', str)
StateType = TypeVar('StateType')
class Entity(Generic[StateType]):
id: EntityId = None
state: StateType = None
@classmethod
def from_state(cls, state: StateType): # ?????
ret = object.__new__(cls)
ret.id = None
ret.state = state
return ret
def assign_id(self, id: EntityId) -> None:
self.id = id
class UserState(NamedTuple):
name: str
age: int
class User(Entity[UserState]):
def __init__(self, name, age) -> None:
super().__init__()
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)