泛型类型类的正确类型提示

时间:2020-07-03 23:22:10

标签: python type-hinting

我试图用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,我收到警告“ {{1}”的警告“类'Node'的未解决的属性引用'get_string'”和“类'Node'的未解决的属性引用'get_integer'” }和n.get_string()行。

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

我尝试在Node.depth_first_search中创建类似于以下内容的“类型转换”方法。

StringNode

这可以解决 def depth_first_search(self) -> Iterable[StringNode]: return super().depth_first_search() 块中的警告,但是现在我收到关于此方法返回值的“预期类型'Iterable [StringNode]',得到了'Iterable [Node]'”警告

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

2 个答案:

答案 0 :(得分:2)

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

在这种情况下,您可能不应该拥有那些get_stringget_integer方法。只需让客户直接访问content,或者如果您出于某种原因被确定要使用吸气剂,请在get_content(self) -> T基类中使其成为Node

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

答案 1 :(得分:0)

如果将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不会给我任何警告。此外,如果我错误地尝试对upper返回的错误类型的对象调用add_fiveNodeMixin.depth_first_search,也会收到警告。