20

In Python, can you have classes with members that are themselves pointers to members of the type of the same class? For example, in C, you might have the following class for a node in a binary tree:

struct node {
    int data;
    struct node* left;
    struct node* right;
} 

How would you equivalently create this in python?

amssage
  • 277
  • 1
  • 2
  • 7
  • 3
    Those are pointers to the class, not the same class. You can't have a class with members of its own type at all in any language. It's an infinite recursion. – JoshD Oct 07 '10 at 00:49
  • You're right. My apologies. The question is still relevant, however. – amssage Oct 07 '10 at 00:51
  • This is not true, look at a simple linked list sample in Java: http://stackoverflow.com/questions/354875/reversing-a-linked-list-in-java-recursively – ridecar2 Oct 07 '10 at 00:55
  • 1
    @ridecar2, those fields are pointers to objects on the heap. – Mike Oct 07 '10 at 01:01
  • @JoshD: You are correct, except for the "at all in any language" part. [Recursive data types](http://en.wikipedia.org/wiki/Recursive_data_type) are in fact quite possible in many functional languages -- that's how Haskell implements linked lists, for example. However, for it to work correctly you either need to be using some kind of union type (so you can define your edge case) or else be using lazy evaluation. – Daniel Pryden Oct 07 '10 at 01:28
  • @Daniel Pryden, look at the second example on that page you linked to. – ridecar2 Oct 07 '10 at 01:32
  • 1
    @ridecar2, @Daniel Pryden: My wording was not exact. In what I said, that can be understood as incorrect. My meaning was more of from the memory layout point of view: That an actual structure of memory can't be contained within itself. In something like Java, references or pointers take care of that matter, which is much what the struct in the question is doing. I didn't mean to say that the recursive data concept was impossible (that's what a binary tree node is after all :) ) – JoshD Oct 07 '10 at 01:41
  • @ridecar2: It seems we are agreeing, not disagreeing. Recursive data types can and do exist. I'm not sure I agree that the Java example is a good one, since Java variables of object type are nullable, while object instances themselves cannot be null, and thus are technically not the same type (in a type-theoretic sense). Since Java does not expose any mechanism to refer to the actual instance type of objects (as opposed to the reference types that refer to them), self-referential variables in Java are in fact references, not unlike the pointers in the C example above. – Daniel Pryden Oct 07 '10 at 01:44
  • @JoshD Nope, Python reference is like pointer. Say when you use it for an argument type, it doesn't matter what it contains. – user26742873 Aug 09 '21 at 16:03

8 Answers8

16

While this, as other answers have pointed out, is not a problem due to the dynamic typing, in fact, for Python3, this is a very real issue when it comes to type annotations. And this will not work (note a type annotation of the method argument):

class A:
    def do_something_with_other_instance_of_a(self, other: A):
        print(type(other).__name__)

instance = A()
other_instance = A()

instance.do_something_with_other_instance_of_a(other_instance)

results in:

   def do_something_with_other_instance_of_a(self, other: A):
   NameError: name 'A' is not defined

more on the nature of a problem here: https://www.python.org/dev/peps/pep-0484/#the-problem-of-forward-declarations

You can use string literals to avoid forward references

enter image description here

Other way is NOT using python3-style type annotation in such cases,

and this is the only way if you have to keep your code compatible with earlier versions of Python.

Instead, for the sake of getting autocompletion in my IDE (PyCharm), you can docstrings like this:

using python docstrings instead of type hints to avoid a class self-reference

Update: alternatively, instead of using docstrings, you can use "type: " annotations in a comment. This will also ensure that mypy static type checking will work (mypy doesn't seem to care about docstrings):

enter image description here

Sergey Grechin
  • 876
  • 11
  • 14
13

I think this might be helpful

from typing_extensions import Self


class Node:
    """Binary tree node."""

    def __init__(self, left: Self, right: Self):
        self.left = left
        self.right = right

typing_extensions offers a Self class to reference class itself which I think is most elegent way to self-reference(PEP 673).


As others have mentioned, you can also use string literals. But it comes to problem when you have multiple type hints.

# python 3.10
var: str | int

And then you write something like

class Node:
    def __init__(self, var: 'Node' | SomeClass):
        self.var = var

It will raise an TypeError: unsupported operand type(s) for |: 'str' and 'type'.

sisF
  • 131
  • 1
  • 3
9

Emulating a C struct in Python (using str instead of int as the data type):

"Declaration":

class Node(object):
    data = None # str
    left = None # Node object or None
    right = None # Node object or None

Usage:

root = Node()
root.data = "foo"

b = Node()
b.data = "bar"
root.left = b

z = Node()
z.data = "zot"
root.right = z
John Machin
  • 81,303
  • 11
  • 141
  • 189
  • 4
    You're declaring class members here, not instance members, aren't you? – Russell Borogove Oct 07 '10 at 01:00
  • Sorry, can somebody please explain the above comment? – amssage Oct 07 '10 at 01:28
  • @amssage: You might want to check out the answers to [this other question](http://stackoverflow.com/questions/2424451/about-python-class-and-instance-variables) and/or read the page from the [Python tutorial on classes](http://docs.python.org/tutorial/classes.html). – Daniel Pryden Oct 07 '10 at 01:34
  • 2
    @Russell Borogove: What did you never realise? – John Machin Oct 07 '10 at 02:26
  • That instances inherit class variables. I think I ran into some confusing behavior when I was first learning Python OO and just avoided using class variables entirely since then. I set up all my instance defaults in __init__. – Russell Borogove Oct 07 '10 at 17:50
  • Confusion probably caused by using a mutable value. And they're not "variable" as far as the instance is concerned until the instance changes it. This means that you can have a whole bunch of "class constants" including lists, dicts, sets which instances treat as read-only reference info. Happiness prevails so long as you obey the "peek but don't poke" rule. – John Machin Oct 07 '10 at 20:19
  • 1
    @Russell Borogove: As an instance of a borogove, perhaps you can enlighten us as to what Lewis Carroll meant by "mimsy" :-) – John Machin Oct 07 '10 at 20:22
  • 1
    A portmanteau word combining 'miserable' and 'flimsy'. http://www.alice-in-wonderland.net/school/alice1019.html – Russell Borogove Oct 07 '10 at 21:30
5

Python is a dynamic language. Attributes can be bound at (almost) any time with any type. Therefore, the problem you are describing does not exist in Python.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
4

This question deserves a revamp for 2021. Let's get some code then run through a breakdown.

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node: 
  data: int
  left: Node
  right: Node

As of Python 3.7, we have PEP 563 -- Postponed Evaluation of Annotations, which means one can use type hints to reference the class, inside the class. It does need to be enabled, which is what our first line of code does.

The @dataclass decorator is part of the Data Classes, also added in 3.7, and gives us a convenient way of defining a class, especially one that is primarily members definitions.

I have used Node rather than node, since class names are usually capitalised in python, to avoid collision with variables and modules. This is a stylistic thing and can be safely ignored most of the time.

Finally, from Python 3.10, you can drop the from __future__... import as it will be enabled by default. The release date for that is... Oh, it's today!

Robino
  • 4,530
  • 3
  • 37
  • 40
2

How would I equivalently create this in python?

class node( object ):
    def __init__( self, data, left, right ):
        self.data = data
        self.left = left
        self.right = right

Since all Python variables are, in effect, typeless references, you don't have to mention up front that left and right are going to be instances of nodes.

Russell Borogove
  • 18,516
  • 4
  • 43
  • 50
2

You can't declare types in Python - therefore, there are no problems declaring types in Python.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
1

http://code.activestate.com/recipes/286239-binary-ordered-tree/ is a sample binary tree created using that structure.

ridecar2
  • 1,968
  • 16
  • 34