绑定到类的Python TypeVar 与绑定类不兼容

问题描述 投票:0回答:1

这是一个相当人为的例子,但总而言之:

  • 我有一个
    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]
python generics mypy python-typing
1个回答
0
投票

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)
© www.soinside.com 2019 - 2024. All rights reserved.