from __future__ import annotations
import logging
from datetime import datetime, UTC
from typing import Any, Generic, Self, Protocol, TypeVar
from pydantic import AwareDatetime, BaseModel
logger = logging.getLogger(__name__)
EventDataT_co = TypeVar('EventDataT_co')
class Event(BaseModel, Generic[EventDataT_co]):
raised_at: AwareDatetime
data: tuple[EventDataT_co, ...]
@classmethod
def from_data(cls, *data: EventDataT_co) -> Self:
return cls(raised_at=datetime.now(UTC), data=data)
class EventRepository(Protocol):
def save_events_unsafe(self, *events: Event[Any]) -> None: # same as `*events: Event[Any]`
"""same"""
def save_events_safe(self, *events: Event[object]) -> None:
"""Only allowed if Event is covariant, but then I can't have a custom constructor"""
def save_events_gen(self, *events: Event[EventDataT_co]) -> None:
"""It implies this is a generic function, when it's more `.data`-agnostic"""
class InheritedEventRepository(EventRepository):
def save_events_unsafe(self, *events: Event[Any]) -> None:
logger.info(str([data.id for event in events for data in event.data])) # Type-checker says ok, dev made an uncaught mistake
def save_events_safe(self, *events: Event[object]) -> None:
logger.info(str([data.id for event in events for data in event.data])) # Type-checker says error: "object" has no attribute "id" [attr-defined]
def save_events_gen(self, *events: Event[EventDataT_co]) -> None:
logger.info(str([data.id for event in events for data in event.data]))
# Type-checker error: "EventDataT_co" has no attribute "id" [attr-defined] if covariant
# No error if invariant
event_1 = Event.from_data(1)
event_2 = Event.from_data('foo')
from typing import reveal_type
print(reveal_type(event_1)) # Event[builtins.int]
print(reveal_type(event_2)) # Event[builtins.str]
InheritedEventRepository().save_events_unsafe(event_1, event_2)
InheritedEventRepository().save_events_safe(event_1, event_2)
# error: Argument 1 to "save_events_safe" of "InheritedEventRepository" has incompatible type "Event[int]"; expected "Event[object]" [arg-type] if invariant
# No error if covariant
InheritedEventRepository().save_events_gen(event_1, event_2)
# error: Cannot infer type argument 1 of "save_events_gen" of "InheritedEventRepository" [misc] if invariant
# No error if covariant
Mypy 抛出:
错误:无法使用协变类型变量作为参数[杂项]
在
def from_data(...)
构造函数行上。我不想使 Event
协变,但这似乎是允许 save_events_safe
被 mypy 接受的唯一方法?我不想在 Event[Any]
中使用 save_events
,因为 save_events
将被子类化(我不希望继承它的开发人员具有 没有类型检查安全性)。最后,save_events_gen(self, *events: Event[EventDataT_co])
与save_events_safe
有同样的问题(仅协变)
这意味着我要么坚持使用一个协变版本,不允许自定义构造函数(而且,Event确实不应该在这种情况之外协变使用),要么是一个不变的版本,迫使我使用 Any不可知函数。我该如何解决这个问题?
这是我自己也遇到过的事情,让我们陈述一下事实:
此代表:
def save_events(*events: Event[T]): ...
满足所有这些条件 - 如果您将通用视为“独立于某些内部部分的属性”,那么“数据不可知”和“通用”实际上是同一件事。
以下代码段:
from __future__ import annotations
from datetime import datetime, UTC
from typing import Generic, Self, TypeVar
from pydantic import AwareDatetime, BaseModel
EventDataT = TypeVar('EventDataT')
class Event(BaseModel, Generic[EventDataT]):
raised_at: AwareDatetime
data: tuple[EventDataT, ...]
@classmethod
def from_data(cls, *data: EventDataT) -> Self:
return cls(raised_at=datetime.now(UTC), data=data)
EventDataT_co = TypeVar('EventDataT_co', covariant=True)
def save_events(*events: Event[EventDataT_co]): ...
class Test:
pass
save_events(Event[Test].from_data(Test()))
...在pyright和mypy严格模式下类型检查对我来说都很好。通过引入一种新的类型 var,我们可以使一个为协变,而另一个则为非协变。希望这有帮助!