Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Self-referencing classes in python?

Tags:

python

class

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?

like image 368
amssage Avatar asked Oct 07 '10 00:10

amssage


5 Answers

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'.

like image 62
sisF Avatar answered Oct 09 '22 07:10

sisF


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

like image 28
Sergey Grechin Avatar answered Oct 09 '22 06:10

Sergey Grechin


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!

like image 25
Robino Avatar answered Oct 03 '22 02:10

Robino


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
like image 9
John Machin Avatar answered Oct 09 '22 07:10

John Machin


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.

like image 7
Ignacio Vazquez-Abrams Avatar answered Oct 09 '22 06:10

Ignacio Vazquez-Abrams