【问题标题】:Proper Type Hints For Generic Type Classes泛型类型类的正确类型提示
【发布时间】:2020-10-24 16:04:11
【问题描述】:

我正在尝试用 Python 编写一个树节点类。我有一个名为Node 的基类,它定义了树语义和实现包含不同类型内容的节点的子类。我想使用类型提示。

这是一个最小的实现,它创建字符串或整数的树,并以深度优先的方式枚举它们。

from typing import TypeVar, Generic, List, Iterable

T = TypeVar("T")


class Node(Generic[T]):
    def __init__(self, content: T):
        self.content = content
        self.children: List[Node[T]] = []

    def depth_first_search(self) -> Iterable["Node[T]"]:
        yield self
        for child in self.children:
            yield from child.depth_first_search()


class StringNode(Node[str]):
    def get_string(self) -> str:
        return self.content


class IntegerNode(Node[int]):
    def get_integer(self) -> int:
        return self.content


if __name__ == "__main__":
    a = StringNode("apple")
    b = StringNode("banana")
    c = StringNode("pear")
    a.children = [b, c]
    for n in a.depth_first_search():
        print(n.get_string())

    a = IntegerNode(1)
    b = IntegerNode(2)
    c = IntegerNode(3)
    a.children = [b, c]
    for n in a.depth_first_search():
        print(n.get_integer())

此代码在运行时有效,但是,从 PyCharm 我收到警告“类 'Node' 的未解析属性引用 'get_string'”和 n.get_string() 的“类 'Node' 的未解析属性引用 'get_integer'”和n.get_integer() 行。

我尝试为类型变量T 指定各种covariantcontravariant 修饰符。在 Python 3.7 中,我还尝试通过添加 from __future__ import annotations 并从 Node.depth_first_search 的返回值提示中删除引号来使用 PEP 563。这些都没有效果。

我尝试在StringNode 中创建类似以下的“类型转换”方法。

    def depth_first_search(self) -> Iterable[StringNode]:
        return super().depth_first_search()

这会处理__main__ 块中的警告,但现在我在此方法的返回值上收到“预期类型'Iterable[StringNode]',得到'Iterable[Node]'”警告。

如何重写类型提示以免收到警告?

【问题讨论】:

  • 是的。在实例变量上也有类型提示没有效果。
  • 因为您的 depth_first_search(self) 总是返回 Node 对象的迭代器。只需直接访问内容就没有必要拥有一个泛型类来创建子类只是为了拥有子类特定的访问器......而且不需要那些子类
  • IOW Node[str]StringNode 不一样
  • 在这个简单的示例中,子类似乎过多,但在我真正的大型应用程序中,以这种方式组织事物很有帮助。
  • 基本上,n 的类型是 Node[str]。要么将 n 转换为 StringNode,要么在 StringNode 内创建一个具有正确返回类型的新函数 depth_first_search

标签: python type-hinting


【解决方案1】:

aStringNode 时,a.depth_first_search() 返回Iterable[Node[str]],而不是Iterable[StringNode]

在这种情况下,您可能不应该使用get_stringget_integer 方法。只需让客户直接访问content,或者如果您出于某种原因确定要使用getter,请在Node 基类中设置get_content(self) -> T

我认为 Python 的类型注释不支持让您的 depth_first_search 返回您想要的子类类型的可迭代对象,而无需显式强制转换或 Any。您需要能够表达selfself.children 的元素具有相同类型的事实,但我看不到这样做的方法。

【讨论】:

    【解决方案2】:

    如果我将Node 设为基类,似乎没有办法使用 Python 的类型提示来获得我想要的结果。但是,我可以通过将 Node 更改为 mixin 来使其工作。

    from typing import TypeVar, Iterable, Generic
    
    T = TypeVar("T")
    
    
    class NodeMixin(Generic[T]):
        def __init__(self, *children: T):
            self.children = children
    
        def depth_first_search(self) -> Iterable[T]:
            yield self
            for child in self.children:
                yield from child.depth_first_search()
    
    
    class StringNode(NodeMixin["StringNode"]):
        def __init__(self, content: str, *children: T):
            super().__init__(*children)
            self.content = content
    
        def upper(self) -> str:
            return self.content.upper()
    
    
    class IntegerNode(NodeMixin["IntegerNode"]):
        def __init__(self, content: int, *children: T):
            super().__init__(*children)
            self.content = content
    
        def add_five(self) -> int:
            return self.content + 5
    
    
    if __name__ == "__main__":
        t = StringNode("apple", StringNode("banana"), StringNode("pear"))
        for n in t.depth_first_search():
            print(n.upper())
    
        t = IntegerNode(1, IntegerNode(2), IntegerNode(3))
        for n in t.depth_first_search():
            print(n.add_five())
    

    这运行正常,PyCharm 没有给我任何警告。此外,如果我在NodeMixin.depth_first_search 返回的错误类型的对象上错误地尝试调用upperadd_five,我确实会收到警告。

    【讨论】:

      猜你喜欢
      • 2016-06-09
      • 1970-01-01
      • 2021-11-25
      • 2019-02-14
      • 2020-11-06
      • 1970-01-01
      • 2021-08-27
      • 2017-08-18
      • 2014-07-15
      相关资源
      最近更新 更多