Python:在 Protocol 和 TypedDict 之间共享类型注释

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

举这个简单的例子:

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"

我想做的事情是不可能的吗?

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

经过一些相对彻底的实验,我确定这似乎是不可能的(尽管可能只是相当困难)。这似乎源于这样一个事实: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
类.

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