0

I tried to implement a tree data structure in Python. This is what I've ended up so far:

class Node:
    """
    This class implements a node in a tree.

    # Arguments #
    metadata:dict=dict()    |   A dict to store metadata.
    subnodes:tuple=tuple()  |   A tuple to define subnodes.
    """

    def __init__(self, metadata={"label": None, "content": None, "description": None}, subnodes=list()):
        self.metadata = metadata
        self.subnodes = subnodes

    def __str__(self):
        return "[{label}] {content}".format(
            label=str(self["label"]),
            content=str(self["content"])
        )

    def __repr__(self):
        return "<{}>".format(str(self))

    def __len__(self):
        count = len(self.subnodes)

        for n in self.subnodes:
            print(n)
            count+=len(n.subnodes)

        return count

    def __contains__(self, o):
        # check if in subnodes of self
        if o in self.subnodes:
            return True

        # check if in subnodes (recursive)
        for n in self.subnodes:
            if o in n:
                return True

        return False

    def __getitem__(self, key):
        return self.metadata[key]

    def __setitem__(self, key, value):
        self.metadata[key] = value

    def __delitem__(self, key):
        del self.metadata[key]

    def __lt__(self, o):
        return self in o

    def __gt__(self, o):
        return o in self

    def append(self, o:"Node"):
        """
        Appends an object as children.

        # Arguments #
        o:Node  |   Node object.
        """

        self.subnodes.append(o)

    def insert(self, i:int, o:"Node"):
        """
        Inserts an object to given index.

        # Arguments #
        i:int   |   Index
        o:Node  |   Node object
        """

        self.subnodes.insert(i, o)

    def remove(self, o:"Node"):
        """
        Removes an object from subnodes.

        # Arguments #
        o:Node  |   Node object
        """

        self.subnodes.remove(o)

    def is_leafnode(self):
        """
        Is this node a leaf node?
        """

        return len(self) == 0

    def search(self, **query):
        """
        Searches tree with metadata.

        **query:dict    |   Searching query.
        """

        return [d for d in self.metadata if all(d.get(k, object()) == v for k, v in query.items())]

I add subnodes by append function of a Node object. However, subnodes attribute acts as if it was a static attribute.

What I expect is:

enter image description here

However, subnodes attribute of all objects returns:

enter image description here

Manual Test

I have tested it by hand. I have 6 Node objects as below:

  • a
  • b
  • c
  • d
  • e
  • f

I wanted to achieve a structure as I demonstrated above. So I made:

a.append(b) # a => b
a.append(c) # a => c

b.append(d) # b => d
b.append(e) # b => e

c.append(f) # c => f

However, when I call subnodes attribute of any Node object:

a.subnodes # [<[b] None>, <[c] None>, <[d] None>, <[e] None>, <[f] None>]
b.subnodes # [<[b] None>, <[c] None>, <[d] None>, <[e] None>, <[f] None>]
# ...
# rest is the same

Environment

  • Python 3.5.1
Eray Erdin
  • 2,633
  • 1
  • 32
  • 66
  • Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](http://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – jwodder Mar 03 '17 at 16:28

1 Answers1

1

Your problem appears to be caused by the Default Parameter Value issue.

What this means is that the default parameter subnodes=list() in the definition __init__ is evaluated exactly one time, during the creation of the class Node. From this point onward, the same mutable object will be used as the default value whenever you instantiate a new object. And as this is a mutable object, whenever you append something to self.subnodes, you're actually modifying the default value. There is a quite detailed discussion in this question.

Basically, the rule in Python is to never use mutable data types as default argument values. Instead, use None, and explicitly assign the default values like so:

class Node:

    def __init__(self, metadata={"label": None, "content": None, "description": None}, subnodes=None):
      self.metadata = metadata
      if subnodes is None:
          self.subnodes = []
      else:
          self.subnodes = subnodes
Community
  • 1
  • 1
Schmuddi
  • 1,995
  • 21
  • 35