Python 3类型提示具有前向引用和正确继承

时间:2017-12-12 11:08:50

标签: python inheritance type-hinting

在2017年PyCharm的Win10x64下使用Python 3.6。

我知道在Python社区中输入(甚至类型提示)是不受欢迎的,但请耐心等待。

假设我有一个Node类来创建具有任意内容的树状容器。这是一个高度简化的版本:

from typing import Any, List

class Node:

    def __init__(self, content: Any, parent: 'Node' = None):
        self.content = content  # type: Any
        self.parent = parent  # type: 'Node'
        self.children = []  # type: List['Node']
        if parent:
            parent.children.append(self)

    @property
    def descendants(self) -> List['Node']:
        """Returns a list of nodes that are descendant from the node starting with its first child, then its
            descendants (and ending with the last descendant of its last child)."""
        out = []
        for ch in self.children:
            out.append(ch)
            for desc in ch.descendants:
                out.append(desc)
        return out

我正在尝试使用正确的类型提示。鉴于我需要指出parent的类型为Node,我使用PEP 484中规定的引号作为前向引用。与self.children相同,使用List['Node'],也使用属性descendants

如果我继承自Node(就像class SpecialNode(Node): pass那样),我希望PyCharm的类型检查器能够正确地推断出所有相关类型,这样就可以预期SpecialNode Node

考虑一下:

n_root = Node('foo')
n_0 = Node(10, parent=n_root)
n_01 = Node('bar', parent=n_0)
n_root_and_children = [n_root] + n_root.children
n_root_and_descendants = [n_root] + n_root.descendants

s_root = SpecialNode('special_foo')
s_0 = Node(-10, parent=s_root)
s_01 = Node('special_bar', parent=s_0)
s_root_and_children = [s_root] + s_root.children
s_root_and_descendants = [s_root] + s_root.descendants

显然,所有这些仍然是"工作"在运行时,但PyCharm在最后一行中强调s_root.descendants并告诉我:

预期类型'列表[SpecialNode]' (匹配的通用类型'列表[TypeVar(' _T')]'),得到'列表[节点]'代替

我做错了什么?

修改

受到现在删除的答案的启发,我使用TypeVar找出了解决方案,但没有完全理解,为什么会有效。如果有人能更好地回答这个问题,我将不胜感激。

from typing import Any, List, TypeVar

NodeType = TypeVar('Node')


class Node:

    def __init__(self, content: Any, parent: NodeType = None):
        self.content = content  # type: Any
        self.parent = parent  # type: NodeType
        self.children = []  # type: List[NodeType]
        if parent:
            parent.children.append(self)

    @property
    def descendants(self) -> List[NodeType]:
        """Returns a list of nodes that are descendant from the node starting with its first child, then its
            descendants (and ending with the last descendant of its last child)."""
        out = []
        for ch in self.children:
            out.append(ch)
            for desc in ch.descendants:
                out.append(desc)
        return out


class SpecialNode(Node):
    pass


n_root = Node('foo')
n_0 = Node(10, parent=n_root)
n_01 = Node('bar', parent=n_0)
n_root_and_children = [n_root] + n_root.children
n_root_and_descendants = [n_root] + n_root.descendants

s_root = SpecialNode('special_foo')
s_0 = Node(-10, parent=s_root)
s_01 = Node('special_bar', parent=s_0)
s_root_and_children = [s_root] + s_root.children
s_root_and_descendants = [s_root] + s_root.descendants

PyCharm让我独自一人,代码正常运行。

2 个答案:

答案 0 :(得分:0)

您刚刚将NodeType的类型设为未知.... NodeType从不存在集合类型。我认为只是混淆pycharm并不是很难。 Mypy仍然给我一大堆问题:

node.py:3: error: String argument 1 'Node' to TypeVar(...) does not match variable name 'NodeType'
node.py:3: error: "object" not callable
node.py:8: error: Invalid type "app.services.node.NodeType"
node.py:8: error: The return type of "__init__" must be None
node.py:10: error: Invalid type "app.services.node.NodeType"
node.py:11: error: Invalid type "app.services.node.NodeType"
node.py:13: error: object has no attribute "children"
node.py:15: error: Invalid type "app.services.node.NodeType"
node.py:20: error: Need type annotation for variable

我可以通过这样编写来解决大多数问题:

from typing import Any, List, TypeVar, Generic, Optional
T = TypeVar('T', bound="Node")


class Node(Generic[T]):

    def __init__(self, content: Any, parent: Optional[T]=None) -> None:
        self.content = content  # type: Any
        self.parent = parent  # type: Optional[T]
        self.children = []  # type: List[T]
        if parent:
            parent.children.append(self)

    @property
    def descendants(self) -> List[T]:
        """Returns a list of nodes that are descendant from the node starting with its first child, then its
            descendants (and ending with the last descendant of its last child)."""
        out = []
        for ch in self.children:
            out.append(ch)
            for desc in ch.descendants:
                out.append(desc)
        return out



class SpecialNode(Node):
    pass


n_root = Node[Node]('foo')
n_0 = Node[Node](10, parent=n_root)
n_01 = Node[Node]('bar', parent=n_0)
n_root_and_children = [n_root] + n_root.children
n_root_and_descendants = [n_root] + n_root.descendants

s_root = SpecialNode('special_foo')
s_0 = Node[SpecialNode](-10, parent=s_root)
s_01 = Node[Node]('special_bar', parent=s_0)
s_root_and_children = [s_root] + s_root.children
s_root_and_descendants = [s_root] + s_root.descendants

但是这给我留下了node.py:12:错误:参数1到"追加" " list"具有不兼容的类型Node [T];期待" T" 但是pycharm接受了它......

这里发生的事情是Node的存储类型也是Node但可以被子类型否决,但这意味着它是动态类型的,所以我们应该只使用类似的东西:Any [T]而不是T?

任何帮助将不胜感激!

P.S:这不是解决这个问题的答案,只是解答为什么它似乎得到解决。

答案 1 :(得分:0)

关于您的编辑,PyCharm似乎在做自己的事情,因为mypy不喜欢此代码:

(mytest) bash-3.2$ mypy test2.py
test2.py:5: error: String argument 1 'Node' to TypeVar(...) does not match variable name 'NodeType'
test2.py:5: error: "object" not callable
test2.py:10: error: Invalid type "test2.NodeType"
test2.py:12: error: Invalid type "test2.NodeType"
test2.py:13: error: Invalid type "test2.NodeType"
test2.py:15: error: NodeType? has no attribute "children"
test2.py:18: error: Invalid type "test2.NodeType"
test2.py:22: error: Need type annotation for 'ch'
(mytest) bash-3.2$

这是在mypy 0.641Python 3.7.1上。

此后您是否重新访问了此编辑/修复程序?