如何使mypy像我的协议一样在运行时使用runtime_checkable工作

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

我定义了几个协议,如下所示:

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 使用这些协议?

python python-typing mypy
1个回答
1
投票

我会一一解决问题。

给定以下类似文件对象的协议:

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
因为除了第一个参数之外,它明确地将其余参数定义为 仅关键字(全部遵循
*
)。

© www.soinside.com 2019 - 2024. All rights reserved.