基类和继承类的类型注释 - Generic 和 TypeVar 是正确的方法吗?

bin*_*unt 3 python generics type-hinting mypy python-typing

假设我有一个基类

from typing import List, Optional

class Node:
    def __init__(self, name: str) -> None:
        self.name = name
        self.children: List['Node'] = []
    ...
Run Code Online (Sandbox Code Playgroud)

和一个子类

class PropertiesNode(Node):
    def __init__(
        self, name: str, properties: List[str], inherit: Optional['PropertiesNode']
    ) -> None:
        Node.__init__(self, name)
        self.properties = set(properties)
        if inherit:
            self.properties.update(inherit.properties)
            self.children = deepcopy(inherit.children)
            for child in self.children:
                child.properties.update(properties)
                # ^ ERR: "Node" has no attribute "properties"  [attr-defined]
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,mypy(正确地)在那里标记了一个错误,因为Node.children明确给出了List[Node].

所以我阅读了泛型类型,在我看来,解决方案是使用TypeVars 和Generic:

from typing import Generic, List, Optional, TypeVar

N = TypeVar('N', bound='Node')
P = TypeVar('P', bound='PropertiesNode')

class Node(Generic[N]):
    def __init__(self: N, name: str) -> None:
        self.name = name
        self.children: List[N] = []

class PropertiesNode(Node[P]):
    def __init__(
        self: P, name: str, properties: List[str], inherit: Optional[P]
    ) -> None:
        Node.__init__(self, name)
        self.properties = set(properties)
        if inherit:
            self.properties.update(inherit.properties)
            self.children = deepcopy(inherit.children)
            for child in self.children:
                child.properties.update(properties)
Run Code Online (Sandbox Code Playgroud)

但是,现在当我实例化这些类时,我得到了

foo = Node("foo")
# ^ ERR Need type annotation for "foo"  [var-annotated]
bar = PropertiesNode("bar", ["big", "green"], None)
# ^ ERR Need type annotation for "bar"  [var-annotated]
Run Code Online (Sandbox Code Playgroud)

现在,我可以通过这样做来沉默这些

foo: Node = Node("foo")
bar: PropertiesNode = PropertiesNode(...)
Run Code Online (Sandbox Code Playgroud)

但为什么这会让它沉默——我不会在那里给我的任何新信息?我想得越多,就越Generic觉得这不是正确的选择,因为事实是:Nodeor的所有实例都与PropertiesNode具有self.children完全相同的类型self。

但是如果我删除Generic[N]from class Node(Generic[N]):,我最终会再次遇到原来的错误:

class PropertiesNode(Node):
    ...
                child.properties.update(properties)
                # ^ ERR "N" has no attribute "properties"  [attr-defined]
Run Code Online (Sandbox Code Playgroud)

Ale*_*ood 6

这里发生了两件事

\n

1. 泛型

\n

注释变量foo: Node,其中Node是泛型类,相当于将其注释为foo: Node[typing.Any]。它将在默认设置上使 MyPy 静音,但如果您选择使用 MyPy 并将一些更严格的标志设置为True(我建议这样做!),您会发现 MyPy 仍然将此类事情标记为错误。

\n

如果您在 MyPy 中运行此命令:

\n
from typing import TypeVar, Generic, List\n\nN = TypeVar(\'N\', bound=\'Node\')\n\nclass Node(Generic[N]):\n    def __init__(self: N, name: str) -> None:\n        self.name = name\n        self.children: List[N] = []\n\nfoo: Node = Node("foo")\nreveal_type(foo)\n
Run Code Online (Sandbox Code Playgroud)\n

您会发现 MyPy 将返回一条类似于以下内容的消息:

\n
Revealed type is "__main__.Node[Any]"\n
Run Code Online (Sandbox Code Playgroud)\n

(NBreveal_type是 MyPy 可以识别的函数,但如果您尝试在运行时使用它,则会失败。)

\n

要让 MyPy 将未参数化的泛型标记为错误,请使用命令行参数运行 MyPy --disallow-any-generics。这样做意味着 MyPy 将标记以下错误:

\n
main.py:3: error: Missing type parameters for generic type "Node"\nmain.py:10: error: Missing type parameters for generic type "Node"\n
Run Code Online (Sandbox Code Playgroud)\n

迫使您将代码调整为以下内容:

\n
from typing import TypeVar, Generic, List, Any\n\nN = TypeVar(\'N\', bound=\'Node[Any]\')\n\nclass Node(Generic[N]):\n    def __init__(self: N, name: str) -> None:\n        self.name = name\n        self.children: List[N] = []\n\nfoo: Node[Any] = Node("foo")\n
Run Code Online (Sandbox Code Playgroud)\n

这让 MyPy 再次高兴,并说了与您在原始代码中所说的相同的事情,但更明确。

\n

然而...

\n

2.我认为在这种情况下没有必要使用泛型

\n

您不必从 generic 继承来用注释self参数。此外,正如您在问题中所说,无论是从 MyPy 的角度还是从阅读您代码的其他人的角度来看,继承在这里并没有真正意义。我会像这样修改你的代码:__init__TypeVarGeneric

\n
from typing import List, Optional, TypeVar, Any\nfrom copy import deepcopy\n    \nN = TypeVar(\'N\', bound=\'Node\')\nP = TypeVar(\'P\', bound=\'PropertiesNode\')\n\nclass Node:\n    def __init__(self: N, name: str, *args: Any, **kwargs: Any) -> None:\n        self.name = name\n        self.children: List[N] = []\n    \nclass PropertiesNode(Node):\n    def __init__(self: P, name: str, properties: List[str], inherit: Optional[P], *args: Any, **kwargs: Any) -> None:\n        super().__init__(name)\n        self.properties = set(properties)\n        if inherit is not None:\n            self.properties.update(inherit.properties)\n            self.children: List[P] = deepcopy(inherit.children)\n            for child in self.children:\n                child.properties.update(properties)\n
Run Code Online (Sandbox Code Playgroud)\n

现在,我们有了让 MyPy 满意的注释,即使是在最严格的设置下,它们甚至对人类也有意义!

\n

注意,我在这里更改了代码中的另外两件事:

\n
    \n
  1. 我按照编写的方式向您的方法 \xe2\x80\x94添加了*args, **kwargs参数,它们违反了里氏替换原则。通过添加这些参数,您可以避免该问题。__init__
  2. \n
  3. 我将你的测试从 \xe2\x80\x94 更改if inherit为python 中的if inherit is not None很多东西都可以是False-y ,因此在测试某个值是否为 -y 时,通过身份进行测试要安全得多None。
  4. \n
\n