举这个简单的例子:
from __future__ import annotations
import typing as t
class MyType:
def __init__(self, s: str, i: int) -> None:
self.s = s
self.i = i
class MyProto(t.Protocol):
s: str
i: int
class MyDict(t.TypedDict):
s: str
i: int
def my_serializer(inst: MyProto) -> MyDict:
return {"s": inst.s, "i": inst.i}
d = my_serializer(MyType("a", 1))
所有类型检查均通过。
现在我们可以说
MyType
实际上是一个具有许多属性的 ORM 类,它是协议和字典类型的真实来源。每次将属性添加到类中时,都必须在 Protocol 类主体和 TypedDict 类主体中维护相同的注释,感觉有点多余。
我想知道是否有一种方法可以集中定义类型注释并告诉 mypy 这些是协议和 dict 类的类型定义。
我试过这个:
class TypeMixin:
s: str
i: int
class MyProto(TypeMixin, t.Protocol):
pass
class MyDict(TypeMixin, t.TypedDict):
pass
然而,mypy 抱怨:
test.py:15: error: All bases of a protocol must be protocols
test.py:19: error: All bases of a new TypedDict must be TypedDict types
...这实际上是运行时的 TypeError。
还有这个:
annos = {"s": "str", "i": "int"}
MyProto = type("MyProto", (t.Protocol,), {"__annotations__": annos})
MyDict = type("MyDict", (t.TypedDict,), {"__annotations__": annos})
def my_serializer(inst: MyProto) -> MyDict:
return {"s": inst.s, "i": inst.i}
这可以运行,但 mypy 会抱怨,而且我认为这对 mypy 来说有点太动态了:
test.py:12: error: Argument 2 to "type" has incompatible type "Tuple[_SpecialForm]"; expected "Tuple[type, ...]"
test.py:13: error: Argument 2 to "type" has incompatible type "Tuple[object]"; expected "Tuple[type, ...]"
test.py:16: error: Variable "topsport.events.test.MyProto" is not valid as a type
test.py:16: error: Variable "topsport.events.test.MyDict" is not valid as a type
test.py:17: error: MyProto? has no attribute "s"
test.py:17: error: MyProto? has no attribute "i"
我想做的事情是不可能的吗?
经过一些相对彻底的实验,我确定这似乎是不可能的(尽管可能只是相当困难)。这似乎源于这样一个事实:Protocol 和 TypedDict 都是结构类型。
通过一些测试,我确定 mypy 要求在其类定义中显式定义 Protocols 和 TypedDicts 的属性。正如您所指出的,Protocols 和 TypedDicts 只能分别从其他 Protocols 和 TypedDicts 继承,因此无法通过继承从
MyType
类生成它们。即使您可以从 MyType
获取 Protocol 或 TypedDict,也无法通过继承创建另一个类:以下两个示例都会产生 TypeError (尽管有趣的是 mypy 仅标记第一个):
from typing import Protocol, TypedDict
class MyProto1(Protocol):
s: str
i: int
class MyDict1(MyProto1, TypedDict): ...
################################################################
class MyDict2(TypedDict):
s: str
i: int
class MyProto2(MyDict2, Protocol): ...
还值得注意的是,定义 TypedDict 后,您无法更新它所需的键,因此在定义后无法动态扩充类的结构:
class MyDict(TypedDict): ...
print(MyDict.__required_keys__) # frozenset()
幸运的是,正如评论中所指出的,您可以走得很远,而不必构造此类额外的类,或者您可以硬着头皮并有一点冗余代码,同时尝试尽可能少地更新
MyType
类.