python classes that refer to each other
Asked Answered
B

9

17

I have two classes that refer to each other, but obviously the compiler complains. Is there any way around this?

EDIT

Actually my code is slightly different than what Hank Gay uses. So python can definitely deal with some kinds of circular references, but it tosses an error in the following situation. Below is what I've got and I get an 'name Y not defined error'

class X(models.Model):

        creator = Registry()
        creator.register(Y)

class Y(models.Model):
    a = models.ForeignKey(X)
    b = models.CharField(max_length=200)

Hope this helps clarify. Any suggestions.

Bloat answered 25/6, 2009 at 20:40 Comment(6)
What's “obvious”? Where does it complain? Show code? Circular references are no problem for Python, the problem lies elsewhere.Velda
What does the code look like?Sidra
What is here circular? Y does not reference X at all as much I can see!Legitimate
In Y ... a = models.ForeignKey(X)Bloat
You seem to be trying to create two models that reference each other with Djangos ORM. I haven't used it, but I'm sure it supports it, and you need to find the documentation for that. It's perfectly doable in SQLAlchemy, I know that.Shortage
Please post ALL the relevant code.Wolfie
M
30

In python, the code in a class is run when the class is loaded.

Now, what the hell does that mean? ;-)

Consider the following code:

class x:
    print "hello"
    def __init__(self): print "hello again"

When you load the module that contains the code, python will print hello. Whenever you create an x, python will print hello again.

You can think of def __init__(self): ... as equivalent with __init__ = lambda self: ..., except none of the python lambda restrictions apply. That is, def is an assignment, which might explain why code outside methods but not inside methods is run.

When your code says

class X(models.Model):
    creator = Registry()
    creator.register(Y)

You refer to Y when the module is loaded, before Y has a value. You can think of class X as an assignment (but I can't remember the syntax for creating anonymous classes off-hand; maybe it's an invocation of type?)

What you may want to do is this:

class X(models.Model):
    pass
class Y(models.Model):
    foo = something_that_uses_(X)
X.bar = something_which_uses(Y)

That is, create the class attributes of X which reference Y after Y is created. Or vice versa: create Y first, then X, then the attributes of Y which depend on X, if that's easier.

Hope this helps :)

Maple answered 25/6, 2009 at 21:46 Comment(0)
E
5

This is a great question. Although others have already answered it, I will feel free to provide another example.

Consider this program.

@dataclass
class A:
    b: B

class B:
    def __init__(self):
        pass

b is now a class-level variable and this program does not work. The name B is not defined at the moment when the Python interpreter loads (executes) the code of class A. Unlike compiled languages (such as C/C++), interpreters execute the code from the beginning to the end of the file command by command, in one pass. Since Python needs to know what B is when it defines the class A, it fails. B is only defined later.

Now, consider a slightly different program.

class A:
    def __init__(self):
        self.b = B()

class B:
    def __init__(self):
        pass

b is now an object-level variable and this program works. Python still executes the code from the beginning to the end of the file in the single pass, however, now it does not need to know what B is at the moment it reads the self.b = B() line. This is because the __init__ method is executed only when someone wants to construct an object of class A. Since the construction of an object will happen somewhere later in the program (when B is already defined), __init__ will work fine when it is needed.

Ellanellard answered 5/7, 2021 at 16:58 Comment(0)
C
2

UPDATE: He changed the question after my answer. The currently accepted solution is better in light of the new question.

What are you saying is the problem?

class A(object):
    def __init__(self):
        super(A, self).__init__()


    def b(self):
        return B()


class B(object):
    def __init__(self):
        super(B, self).__init__()


    def a(self):
        return A()

This compiles and runs just fine.

Cheque answered 25/6, 2009 at 20:45 Comment(1)
Some additional information: This works, yes. But it comes with a price: It only works if you put all classes into a single file. This is quite inconvenient: It might be very pythonic, I don't know, but I do know it breaks with good OOP practice of using an own file for each (public) class.Navar
B
2

As long as you are working within a method you can access the class object.

Thus the example above has no problems if creator.register(Y) is moved inside __init__. However, you cannot have circular references to classes outside of methods.

Bloat answered 25/6, 2009 at 21:31 Comment(1)
This answer doesn't explain anything. It gives a "workaround" without understanding or explaining the real issue, which is explained in the answers by Jonas and John Machin.Kinesiology
Y
2

The error is that execution of creator.register(Y) is attempted during the (executable) definition of class X, and at that stage, class Y is not defined. Understand this: class and def are statements that are executed (typically at import time); they are not "declarations".

Suggestion: tell us what you are trying to achieve -- perhaps as a new question.

Yoong answered 25/6, 2009 at 21:48 Comment(0)
I
0

I came here late, but I want to show how I resolved this problem.

You can have a class nested to the other, and both classes will be able to reference each other

Here a demonstration:

class X():

    class Y():
        def x(self):
            return X()

    def y(self):
        return self.Y()

a1 = X()
a2 = a1.y()

b1 = X.Y()
b2 = b1.x()

And you can create a BaseClass that contains the classes you want to referenciate

class Z():

    class X():
        def y(self):
            return Z.Y()

    class Y():
        def x(self):
            return Z.X()
Incombustible answered 10/12, 2022 at 0:2 Comment(0)
D
0

You need to put all classes into different files (X.py, Y.py) inside one module (one dir) and create __init__.py in the same dir

__init__.py:

from model.X import X
from model.Y import Y

This way both classes will be in the same module and will be imported similarly as they would be in one file

Next you can use it in code: import model

x = model.X()
y = model.Y()
Defeasible answered 16/9, 2023 at 9:54 Comment(0)
K
-2

As we well know, it looks like a self-evident contradiction that we try to imagine of two independent but inter-dependent entities just from a point of their birth in physical world. But, when it comes to the area of software, we often encounter this kind of issues so called 'circular or mutual references'. That may come more seriously in object-oriented design, in which inter-operating software elements are usually defined and related to one another in imitation of such a way as physical ones, but still as pure logical existences.

In many programming languages, these issues have been resolved by declaring to-be-referenced elements in time before their to-reference elements just in form of signatures (no body definitions) for functions or classes. However, that sort of evading tricks seems neither longer available nor useful for a script-based language like Python.

In Python, we'd better approach 'circular reference' in a view of software engineering, as follows:

  1. It's much better to redesign classes not circular if possible; there are several ways (e.g. class de- or composition, call-back function, observer or subscriber patterns, and etc.) to make references between elements occur within the same class, removed or inverse.

  2. In cases that linearizing some circular chains between elements might cause more serious problem in some aspect like quality or productivity, we can take another measure to separate their traditional construction phase into two: creating and structuring. For example, two persons in friends who are destined to have their birth absolutely after observing the other's birth can do in a way that they are first born and then have their friendship just before any meaningful and observable occasions happen. Note that if we face some extreme complexity or need some high degree of integrity in dealing with inter-aggregated objects, applying a Factory pattern will pay off.

Koralle answered 25/1, 2019 at 23:56 Comment(0)
L
-4

The problem is most likely not Python. I would think it is an SQL issue. The classes are via an abstraction layer converted to an SQL query to create a table. You are trying to reference from one table another one that at the time does not exist yet.

In SQL you would solve this by creating the table first without the references and after that modify them to make those references,

However I am not sure about my answer, so take it with lots of seasoning, I would be actually quite surprised if Django's database abstraction layer doesn't deal with cross references nicely.

Lactobacillus answered 25/6, 2009 at 23:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.