Tree structure with support for inorder and preorder traversal
Your instinct to question the appropriateness of tree.size(tree) is right.  You should be able to write tree.size() instead.  The problem is that you've written the methods to take a node parameter, when in fact self is a Node.

Therefore, you should define

def size(self):
    return 1 + (self.left.size()  if self.left  is not None else 0) \
             + (self.right.size() if self.right is not None else 0)


so that you can write tree.size().



The __str__ method isn't right: there's nothing that says that data is a string.  (In fact, in your example, they are integers.)  A more reasonable implementation would be

def __str__(self):
    return str(self.data)




It would be better to avoid hard-coding the print() calls in inorder and preorder, so that they can be used to do more than just printing.  In Python, it would be better to yield each datum, so that inorder() and preorder() act as generators.  (Note that this uses yield from, introduced in Python 3.3.)

def inorder(self):
    if self.left is not None:
        yield from self.left.inorder()
    yield self.data
    if self.right is not None:
        yield from self.right.inorder()


… to be called like

for data in tree.inorder():
    print(data)


You would then have the flexibility to write something like sum(data for data in tree.inorder()) to add all of the nodes.