举这个简单的例子:
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"
我想做的事情是不可能的吗?
你想要实现的目标是绝对有可能的! 你面临的问题是
typing.Protocol
和
typing.TypedDict
的性质。 它们是特殊的类型,对其基类有限制。
让我们探讨一下如何使用以下策略实现你的目标:
- 使用类装饰器:
```python from future import annotations import typing as t
def add_shared_annotations(cls): cls. annotations = {"s": str, "i": int} return cls
@add_shared_annotations class MyProto(t.Protocol): pass
@add_shared_annotations class MyDict(t.TypedDict): pass
class MyType: def init (self, s: str, i: int) -> None: self.s = s self.i = i
def my_serializer(inst: MyProto) -> MyDict: return {"s": inst.s, "i": inst.i}
d = my_serializer(MyType("a", 1)) ```
在这个例子中,我们创建了一个装饰器
add_shared_annotations
,它接受一个类并添加共享的注解。 这让你可以在一个位置定义注解,并将其应用于
MyProto
和
MyDict
。
- 使用帮助函数生成类型:
```python from future import annotations import typing as t
def create_shared_types() -> tuple[t.Type[t.Protocol], t.Type[t.TypedDict]]: shared_annotations = {"s": str, "i": int} MyProto = type("MyProto", (t.Protocol,), shared_annotations) MyDict = type("MyDict", (t.TypedDict,), shared_annotations) return MyProto, MyDict
MyProto, MyDict = create_shared_types()
class MyType: def init (self, s: str, i: int) -> None: self.s = s self.i = i
def my_serializer(inst: MyProto) -> MyDict: return {"s": inst.s, "i": inst.i}
d = my_serializer(MyType("a", 1)) ```
这里,我们使用
create_shared_types
函数在运行时动态生成
MyProto
和
MyDict
类型。 虽然这避免了重复,但它可能比类装饰器方法更难理解。
为你的用例选择最佳方法取决于你的偏好和项目的具体需求。
两种方法都允许你集中定义类型注释并在
Protocol
和
TypedDict
之间共享,同时保持 mypy 的类型安全性。