我定义了几个协议,如下所示:
import json
from typing import Any, Protocol, TypeVar, runtime_checkable
T_co = TypeVar('T_co', covariant=True)
T = TypeVar('T')
@runtime_checkable
class SupportsRead(Protocol[T_co]):
def read(self, __length: int = ...) -> T_co: ...
@runtime_checkable
class SupportsWrite(Protocol[T_co]):
def write(self, data: str | bytes): ...
@runtime_checkable
class SerializerToString(Protocol):
def dumps(self, value: Any, *argv, **kwargs) -> str: ...
def loads(self, value: str | bytes, *argv, **kwargs) -> Any: ...
@runtime_checkable
class SerializerToFile(Protocol):
def dump(self, value: Any, file: SupportsWrite[str | bytes], **kwargs) -> None: ...
def load(self, file: SupportsRead[str | bytes], **kwargs) -> Any: ...
@runtime_checkable
class Serializer(SerializerToString, SerializerToFile, Protocol):
pass
class MySerializer:
def dumps(self, value: Any) -> str:
return f"dumps {value}"
def loads(self, value: str) -> Any:
return f"loads {value}"
var1: SerializerToFile = json
var2: SerializerToString = json
var3: Serializer = json
var4: SerializerToString = MySerializer()
assert isinstance(var1, SerializerToFile)
assert isinstance(var2, SerializerToString)
assert isinstance(var3, Serializer)
assert isinstance(var4, SerializerToString)
print("everything ok")
它运行时没有错误,但 mypy 将所有分配标记为
var1
-var4
,并出现如下错误:
test11.py:42: error: Incompatible types in assignment (expression has type Module, variable has type "SerializerToFile") [assignment]
test11.py:42: note: Following member(s) of "Module json" have conflicts:
test11.py:42: note: Expected:
test11.py:42: note: def dump(value: Any, file: SupportsWrite[Union[str, bytes]], **kwargs: Any) -> None
test11.py:42: note: Got:
test11.py:42: note: def dump(obj: Any, fp: SupportsWrite[str], *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwds: Any) -> None
test11.py:42: note: Expected:
test11.py:42: note: def load(file: SupportsRead[Union[str, bytes]], **kwargs: Any) -> Any
test11.py:42: note: Got:
test11.py:42: note: def load(fp: SupportsRead[Union[str, bytes]], *, cls: Optional[Type[JSONDecoder]] = ..., object_hook: Optional[Callable[[Dict[Any, Any]], Any]] = ..., parse_float: Optional[Callable[[str], Any]] = ..., parse_int: Optional[Callable[[str], Any]] = ..., parse_constant: Optional[Callable[[str], Any]] = ..., object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ..., **kwds: Any) -> Any
test11.py:43: error: Incompatible types in assignment (expression has type Module, variable has type "SerializerToString") [assignment]
test11.py:43: note: Following member(s) of "Module json" have conflicts:
test11.py:43: note: Expected:
test11.py:43: note: def dumps(value: Any, *argv: Any, **kwargs: Any) -> str
test11.py:43: note: Got:
test11.py:43: note: def dumps(obj: Any, *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwds: Any) -> str
test11.py:43: note: Expected:
test11.py:43: note: def loads(value: Union[str, bytes], *argv: Any, **kwargs: Any) -> Any
test11.py:43: note: Got:
test11.py:43: note: def loads(s: Union[str, bytes, bytearray], *, cls: Optional[Type[JSONDecoder]] = ..., object_hook: Optional[Callable[[Dict[Any, Any]], Any]] = ..., parse_float: Optional[Callable[[str], Any]] = ..., parse_int: Optional[Callable[[str], Any]] = ..., parse_constant: Optional[Callable[[str], Any]] = ..., object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ..., **kwds: Any) -> Any
test11.py:44: error: Incompatible types in assignment (expression has type Module, variable has type "Serializer") [assignment]
test11.py:44: note: Following member(s) of "Module json" have conflicts:
test11.py:44: note: Expected:
test11.py:44: note: def dump(value: Any, file: SupportsWrite[Union[str, bytes]], **kwargs: Any) -> None
test11.py:44: note: Got:
test11.py:44: note: def dump(obj: Any, fp: SupportsWrite[str], *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwds: Any) -> None
test11.py:44: note: Expected:
test11.py:44: note: def dumps(value: Any, *argv: Any, **kwargs: Any) -> str
test11.py:44: note: Got:
test11.py:44: note: def dumps(obj: Any, *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwds: Any) -> str
test11.py:44: note: <2 more conflict(s) not shown>
test11.py:45: error: Incompatible types in assignment (expression has type "MySerializer", variable has type "SerializerToString") [assignment]
test11.py:45: note: Following member(s) of "MySerializer" have conflicts:
test11.py:45: note: Expected:
test11.py:45: note: def dumps(self, value: Any, *argv: Any, **kwargs: Any) -> str
test11.py:45: note: Got:
test11.py:45: note: def dumps(self, value: Any) -> str
test11.py:45: note: Expected:
test11.py:45: note: def loads(self, value: Union[str, bytes], *argv: Any, **kwargs: Any) -> Any
test11.py:45: note: Got:
test11.py:45: note: def loads(self, value: str) -> Any
Found 4 errors in 1 file (checked 1 source file)
有没有办法让 mypy 使用这些协议?
我会一一解决问题。
给定以下类似文件对象的协议:
from typing import Any, Protocol, TypeVar, runtime_checkable
T_co = TypeVar('T_co', covariant=True)
@runtime_checkable
class SupportsRead(Protocol[T_co]):
def read(self, __length: int = ...) -> T_co: ...
@runtime_checkable
class SupportsWrite(Protocol[T_co]):
def write(self, data: str | bytes) -> Any: ...
SerializerToFile
只需调整协议成员的参数名称以反映
json
模块中相应函数的参数名称即可修复问题:
@runtime_checkable
class SerializerToFile(Protocol):
def dump(
self,
obj: Any, # was called `value`
fp: SupportsWrite[str | bytes], # was called `file`
**kwargs: Any, # was missing `Any` (optional)
) -> None: ...
def load(
self,
fp: SupportsRead[str | bytes], # was called `file`
**kwargs: Any, # was missing `Any` (optional)
) -> Any: ...
import json
var1: SerializerToFile = json # passes
SerializerToString
同样,名称不匹配,而且您的协议方法也接受任意位置参数,而
loads
和 dumps
函数都不会:
@runtime_checkable
class SerializerToString(Protocol):
def dumps(
self,
obj: Any, # was called `value`
# *argv: Any, # this needs to be removed
**kwargs: Any, # was missing `Any` (optional)
) -> str: ...
def loads(
self,
s: str | bytes, # was called `value`
# *argv: Any, # this needs to be removed
**kwargs: Any, # was missing `Any` (optional)
) -> Any: ...
import json
var1: SerializerToString = json # passes
Serializer
(交叉路口)使用上面的固定协议,它们两者的交集也是用于
json
模块的有效注释:
...
@runtime_checkable
class Serializer(SerializerToString, SerializerToFile, Protocol):
pass
import json
var1: Serializer = json # passes
现在需要对自定义序列化器进行相应调整,使其也与协议匹配:
...
class MySerializer:
def dumps(self, obj: Any, **_kwargs: Any) -> str:
return f"dumps {obj}"
def loads(self, s: str | bytes, **_kwargs: Any) -> Any:
return f"loads {s!r}"
var1: SerializerToString = MySerializer() # passes
第一个
loads
参数必须也接受bytes
,因为协议将其定义为str | bytes
。子类型的方法参数是逆变的。所以你只能加宽序列化器接受的参数类型,而不能缩小它。像 str | bytes | tuple[Any]
这样的愚蠢的东西对于 loads
方法的第一个参数仍然有效,但只是 str
不起作用。
Callable
类型相反,在该类型中,您只能定义可调用的某些 positions 中参数的类型,协议方法更加具体,因为 Python 确实允许函数的所有“正常”参数调用作为关键字参数传递(即按名称)。
例外情况是通过
*args
定义的可变数量位置参数,以及通过将其参数放在签名中的 /
之前专门定义为 仅位置的参数。
因此,如果你说某物是例如的结构子类型
SerializerToFile
,如果它实现了方法dumps
,其中第一个参数是“位置或关键字”并命名为value
,那么您可以在某处使用该类型的对象并将该方法称为dumps(value=123)
。但是 json.dumps
方法不能这样调用,因为它的第一个 parameter
被命名为 obj
,所以调用 json.dumps(obj={})
可以工作,而调用 json.dumps(value={})
会失败。因此json
不符合协议。
类似地,如果您的协议允许在方法中使用“var.args”,即
dumps(obj: Any, *args: Any, **kwargs: Any)
,则意味着您可能希望在该类型的实例上调用该方法作为 dumps({}, 1, 2, 3, 4)
,但不允许 json.dumps
因为除了第一个参数之外,它明确地将其余参数定义为 仅关键字(全部遵循 *
)。