3

I'm trying to create a tree structure with python typing annotation. The code is like this:

from typing import List

class TNode:
    def __init__(self, parent: 'TNode', data: str, children: List['TNode'] = []):
        self.parent = parent
        self.data = data
        self.children = children


root = TNode(None, 'example')

But the code has a issue of type mismatching, Pycharm will raise Expected type 'TNode', got 'None' instead. Is there a way to solve this issue, or if there is a better way to design the class constructor?

hsc
  • 1,226
  • 11
  • 24
  • There are many Python modules available that implement this feature. I also don't recall type annotations to influence behaviour. However, from your code I ama assuming the first Node is a parent node. Maybe you could replace the type annotations with `parent: ('TNode', None)`. – N Chauhan Aug 09 '18 at 09:50
  • @NChauhan: implement *what* feature? This is about type hinting and how to use it correctly. Type hints are a standard Python library feature, no additional modules needed to install. – Martijn Pieters Aug 09 '18 at 09:52
  • @MartijnPieters I meant the 'tree structure' has already been implemented – N Chauhan Aug 09 '18 at 09:55
  • @NChauhan: right, but the vast majority of cases like these are asked by people using such a structure to learn the ropes of Python coding. Using an external library instead would defeat that purpose. – Martijn Pieters Aug 09 '18 at 09:57

1 Answers1

8

If your parent node can be None, you need to mark the argument as Optional or explicitly use a Union[None, 'TNode'] annotation:

from typing import List, Optional

class TNode:
    def __init__(self, parent: Optional['TNode'], data: str, children: List['TNode'] = []) -> None:

Side note: you probably do not want to use [] as a default value for children. Defaults are evaluated once and stored with the function object, so if you were to use the default value and mutate it, you mutate the shared default. See "Least Astonishment" and the Mutable Default Argument.

Set children to a default None sentinel value instead:

class TNode:
    def __init__(
        self,
        parent: Optional['TNode'],
        data: str,
        children: Optional[List['TNode']] = None
    ) -> None:
        self.parent = parent
        self.data = data
        self.children = children or []

The children or [] expression will set self.children to an empty list whenever the children argument is a falsey value, including None and an empty list.

I also used a different formatting for the argument list, better suited for type-annotated arguments where the line length would exceed the recommended 80 characters line length limit.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343