3

Working with Python 3.6 under Win10x64 in PyCharm 2017.

I know typing (or even type hinting) is kind of unpopular in the Python community, but please bear with me.

Let's say I have a Node class to create tree-like containers with arbitrary contents. Here is a highly simplified version:

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

I am trying to use proper type hints. Seeing as I need to indicate the type of parent to be Node, I used the quotes as prescribed in PEP 484 for forward references. Same with self.children, using List['Node'], and also with the property descendants.

If I inherit from Node (like so class SpecialNode(Node): pass), I would expect PyCharm's type checker to be able to infer all the relevant types correctly, such that SpecialNode is expected in place of Node.

Consider this:

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

Obviously all of this still "works" at runtime, but PyCharm underlines s_root.descendants in the last line and tells me:

Expected type 'List[SpecialNode]' (matched generic type 'List[TypeVar('_T')]'), got 'List[Node]' instead

What am I doing wrong?

.

EDIT:

Inspired by an answer that is now deleted, I figured out the solution using TypeVar, but do not fully understand, why it works. I would appreciate, if anyone could answer this better.

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 leaves me alone and the code works properly.

JohnGalt
  • 797
  • 1
  • 9
  • 21
  • check out this related question for forward referencing (and accepted answer) https://stackoverflow.com/questions/33837918/type-hints-solve-circular-dependency. It also states that python 3.7+ has a cleaner solution to the problem. – Daniel Dror Apr 29 '19 at 13:46

1 Answers1

0

With respect to your edit, it looks like PyCharm is doing its own thing because mypy does not like this code:

(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$

This is on mypy 0.641 and Python 3.7.1.

Have you revisited this edit/fix since?

EmmEff
  • 7,753
  • 2
  • 17
  • 19