在签名中具有 TypeVars 的协议的实现者不能使用自己的类型代替。

huangapple go评论95阅读模式
英文:

Implementers of a Protocol with TypeVars in signatures can't use their own types instead

问题

我试图为我的协议的用户提供一种使用任何他们想要的类型作为某个任意数据结构的键和值的方法。

通常情况下,它将是一个字典(Dict),但也可以轻松地是一些外部数据存储,比如 Redis。

我尝试使用 Protocol 来向用户提供这些选项,但是 mypy 不允许实现者在方法签名中使用他们自定义的类型。

有没有办法修复这个类型问题,或者我在根本上使用了错误的工具?

我正在使用 Python 3.11,以下是脚本:

from typing import (
    Dict,
    Generic,
    Optional,
    Protocol,
    Type,
    TypeVar,
    Union,
    runtime_checkable,
)

Key = TypeVar("Key")
Value = TypeVar("Value")


@runtime_checkable
class Data(Protocol):
    async def get_value(self, key: Key) -> Optional[Value]:
        ...

    async def set_value(self, key: Key, value: Value) -> None:
        ...


class DataUser(Generic[Key, Value]):
    def __init__(
        self,
        cls_data: Type[Data],
    ) -> None:
        self.__data = cls_data()

    async def get_value(self, key: Key) -> Optional[Value]:
        return await self.__data.get_value(key)

    async def set_value(self, key: Key, value: Value) -> None:
        await self.__data.set_value(key=key, value=value)


KVDataValue = Union[str, int, float]


class KVData:
    def __init__(self) -> None:
        self.__data: Dict[str, KVDataValue] = {}

    async def get_value(self, key: str) -> Optional[KVDataValue]:
        return self.__data.get(key)

    async def set_value(self, key: str, value: KVDataValue) -> None:
        self.__data[key] = value


data_user = DataUser[str, KVDataValue](
    cls_data=KVData,
)

在这段代码上运行 mypy 会产生以下错误:

src/aiomon/example.py:57:14: error: Argument "cls_data" to "DataUser" has incompatible type "Type[KVData]"; expected "Type[Data]"  [arg-type]
        cls_data=KVData,
                 ^~~~~~
Found 1 error in 1 file (checked 11 source files)

值得注意的是,我正在使用 PyCharm 作为我的 IDE,它显示这是正确的类型。此外,例如,当我从 KVData 中删除 set_value 方法时,它会正确地识别到这个类不再符合 Data 协议。

英文:

I'm trying to give users of my protocols a way to use whatever they want as types for keys and values of some arbitrary data structure.

Usually it will be a Dict, but it can easily be some external data storage like Redis.

I tried using Protocol to give this options to users, but mypy doesn't allow implementers to use their custom defined types in methods singnatures.

Is there a way to fix this typing or am I fundamentally using wrong tools here?

I'm using Python 3.11 and here's the script:

from typing import (
    Dict,
    Generic,
    Optional,
    Protocol,
    Type,
    TypeVar,
    Union,
    runtime_checkable,
)

Key = TypeVar("Key")
Value = TypeVar("Value")


@runtime_checkable
class Data(Protocol):
    async def get_value(self, key: Key) -> Optional[Value]:
        ...

    async def set_value(self, key: Key, value: Value) -> None:
        ...


class DataUser(Generic[Key, Value]):
    def __init__(
        self,
        cls_data: Type[Data],
    ) -> None:
        self.__data = cls_data()

    async def get_value(self, key: Key) -> Optional[Value]:
        return await self.__data.get_value(key)

    async def set_value(self, key: Key, value: Value) -> None:
        await self.__data.set_value(key=key, value=value)


KVDataValue = Union[str, int, float]


class KVData:
    def __init__(self) -> None:
        self.__data: Dict[str, KVDataValue] = {}

    async def get_value(self, key: str) -> Optional[KVDataValue]:
        return self.__data.get(key)

    async def set_value(self, key: str, value: KVDataValue) -> None:
        self.__data[key] = value


data_user = DataUser[str, KVDataValue](
    cls_data=KVData,
)

Running mypy on this code results in:

src/aiomon/example.py:57:14: error: Argument "cls_data" to "DataUser" has incompatible type "Type[KVData]"; expected "Type[Data]"  [arg-type]
        cls_data=KVData,
                 ^~~~~~
Found 1 error in 1 file (checked 11 source files)

Worth noting that I'm using PyCharm as my IDE and it shows that this is correct typing. Moreover when I, for example, remove set_value method from KVData it correctly identifies that this class stopped adhering to the Data Protocol.

答案1

得分: 2

如果协议应该在其方法接受(和返回)的参数方面是通用的,您应该将其定义为通用的。 (参见PEP 544中的“通用协议”

毕竟,DataUser 已经是通用的,(如果我正确理解您的用例)其类型参数应该与底层的 Data 类绑定。

这似乎正常工作:

from typing import Generic, Optional, Protocol, TypeVar, Union, runtime_checkable

K = TypeVar("K", contravariant=True)
V = TypeVar("V")


@runtime_checkable
class Data(Protocol[K, V]):
    async def get_value(self, key: K) -> Optional[V]: ...

    async def set_value(self, key: K, value: V) -> None: ...


class DataUser(Generic[K, V]):
    def __init__(self, cls_data: type[Data[K, V]]) -> None:
        self.__data = cls_data()

    async def get_value(self, key: K) -> Optional[V]:
        return await self.__data.get_value(key)

    async def set_value(self, key: K, value: V) -> None:
        await self.__data.set_value(key=key, value=value)


KVDataValue = Union[str, int, float]


class KVData:
    def __init__(self) -> None:
        self.__data: dict[str, KVDataValue] = {}

    async def get_value(self, key: str) -> Optional[KVDataValue]:
        return self.__data.get(key)

    async def set_value(self, key: str, value: KVDataValue) -> None:
        self.__data[key] = value


data_user = DataUser[str, KVDataValue](cls_data=KVData)

通过 mypy --strict 没有错误。

英文:

If the protocol is supposed to be generic in terms of what arguments its methods accept (and return), you should define it as such. (see "Generic Protocols" in PEP 544)

After all, DataUser is already generic and (if I understand your use case correctly) its type parameters should be bound to those of the underlying Data class.

This seems to work just fine:

from typing import Generic, Optional, Protocol, TypeVar, Union, runtime_checkable

K = TypeVar("K", contravariant=True)
V = TypeVar("V")


@runtime_checkable
class Data(Protocol[K, V]):
    async def get_value(self, key: K) -> Optional[V]: ...

    async def set_value(self, key: K, value: V) -> None: ...


class DataUser(Generic[K, V]):
    def __init__(self, cls_data: type[Data[K, V]]) -> None:
        self.__data = cls_data()

    async def get_value(self, key: K) -> Optional[V]:
        return await self.__data.get_value(key)

    async def set_value(self, key: K, value: V) -> None:
        await self.__data.set_value(key=key, value=value)


KVDataValue = Union[str, int, float]


class KVData:
    def __init__(self) -> None:
        self.__data: dict[str, KVDataValue] = {}

    async def get_value(self, key: str) -> Optional[KVDataValue]:
        return self.__data.get(key)

    async def set_value(self, key: str, value: KVDataValue) -> None:
        self.__data[key] = value


data_user = DataUser[str, KVDataValue](cls_data=KVData)

Passes mypy --strict without errors.

huangapple
  • 本文由 发表于 2023年6月19日 01:21:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76501748.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定