这是一个相当人为的例子,但总而言之:
Store
负责返回 T
类型的值(Model
Store
可以包含Model
的任何子类,但允许您注册转换程序,以便它始终返回T
from collections.abc import Callable, Generator
from dataclasses import dataclass
from typing import Generic, TypeVar
@dataclass
class Model:
pass
@dataclass
class EntryV1(Model):
field: int
@dataclass
class EntryV2(Model):
field: str
T = TypeVar("T", bound=Model)
U = TypeVar("U", bound=Model)
class Store(Generic[T]):
def __init__(self, model: type[T], entries: list[Model]) -> None:
self.model = model
self.entries = entries
self.converters: dict[str, Callable[[Model], T]] = {}
def register_converter(self, old: type[U], converter: Callable[[U], T]) -> None:
self.converters[old.__name__] = converter
def _convert(self, entry: Model) -> T:
if isinstance(entry, self.model):
return entry
else:
converter = self.converters[entry.__class__.__name__]
return converter(entry)
def get(self, idx: int) -> T:
return self._convert(self.entries[idx])
def get_all(self) -> Generator[T, None, None]:
return (self._convert(entry) for entry in self.entries)
store = Store(EntryV2, [EntryV1(field=1), EntryV2(field="2")])
store.register_converter(EntryV1, lambda entry: EntryV2(field=str(entry.field)))
print(store.get(0))
print(list(store.get_all()))
运行此代码时输出:
EntryV2(field='1')
[EntryV2(field='1'), EntryV2(field='2')]
我遇到的问题是
mypy
对代码不满意,尽管 U
绑定到 Model
file.py:32: error: Incompatible types in assignment (expression has type "Callable[[U], T]", target has type "Callable[[Model], T]") [assignment]
我在
U
中使用 register_converter
,因为我想确保 old
的类型与 coverter
的类型兼容
def register_converter(self, old: type[U], converter: Callable[[U], T]) -> None:
编辑
有趣的是,如果我将错误类型的转换器传递给
register_converter
mypy 能够发现它是不正确的并推断出相关类型(例如 register_converter(Entity1, converter)
Argument 2 to "register_converter" of "Store" has incompatible type "Callable[[EntryV2], EntryV2]"; expected "Callable[[EntryV1], EntryV2]
让 mypy 满意的最简单的修复方法是更改
self.converters
的类型,允许 Callable
接受 Any
的参数
class Store(Generic[T]):
def __init__(self, model: type[T], entries: list[Model]) -> None:
self.model = model
self.entries = entries
self.converters: dict[str, Callable[[Any], T]] = {}
虽然这解决了问题,但这意味着理论上我们将来可能会添加错误类型的
Callable
。一个更复杂但类型安全的修复方法是创建一个强制执行该接口的自定义字典类:
from collections import UserDict
from collections.abc import Callable, Generator
from dataclasses import dataclass
from typing import Any, Generic, TypeVar
@dataclass
class Model:
field: Any
@dataclass
class EntryV1(Model):
field: int
@dataclass
class EntryV2(Model):
field: str
T = TypeVar("T", bound=Model)
U = TypeVar("U", bound=Model)
class ConversionDict(UserDict[type[Model], Callable[[Any], T]]):
def __setitem__(self, key: type[U], value: Callable[[U], T]) -> None:
return super().__setitem__(key, value)
def __getitem__(self, key: type[U]) -> Callable[[U], T]:
return super().__getitem__(key)
class Store(Generic[T]):
def __init__(self, model: type[T], entries: list[Model]) -> None:
self.model = model
self.entries = entries
self.converters: ConversionDict[T] = ConversionDict()
def register_converter(self, old: type[U], converter: Callable[[U], T]) -> None:
self.converters[old] = converter
def _convert(self, entry: Model) -> T:
if isinstance(entry, self.model):
return entry
else:
converter = self.converters[entry.__class__]
return converter(entry)
def get(self, idx: int) -> T:
return self._convert(self.entries[idx])
def get_all(self) -> Generator[T, None, None]:
return (self._convert(entry) for entry in self.entries)