英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论