1

When using classes like this:

class Parent:
    def __init__(self):
        self.child = Child()
class Child:
    def __init__(self):
        print(self.parent)  # error, I don't have any way to know my parent!
p = Parent()

of course, it returns an error because the Child instance has no attribute parent, this is normal.

One solution is simply to define the classes like this:

class Parent:
    def __init__(self):
        self.child = Child(self)
class Child:
    def __init__(self, parent):
        self.parent = parent
        print(self.parent)  # now it's ok!

But in my code, I have many classes, and it is somehow annoying to have to pass self as parameter, all the time, when instantiating child objects:

self.child = Child(self)  # pass self so that this Child instance will know its parent

and I would prefer to keep simply:

self.child = Child()      # can this Child instance know its parent without passing self?

Is there a way for Python to automatically infer that an object is in fact an attribute of a parent object, without having to pass self explicitely? If so, how to get the parent?


Example: in GUI programming when creating a Button instance, which is a member of a Panel, which is itself a member of a Window, etc. it seems we have to pass self to children constructors all the time.

Note: I'd like to avoid using inspect because the solutions with it seem to be poorer than just passing self.

Basj
  • 41,386
  • 99
  • 383
  • 673
  • How many parent instances will you have? Just one? – Robert Kearns May 26 '20 at 23:29
  • This is not OOP parent/child relationship. This is caller/callee relationship. The `inspect` module will help you with that. – norok2 May 26 '20 at 23:30
  • 1
    As an aside, you are creating circular references that may keep the objects from ever being freed (at least they'll have to wait for the garbage collector). Consider using a [weakref](https://docs.python.org/3/library/weakref.html) to the parent. – tdelaney May 26 '20 at 23:30
  • @RobertKearns If I remember well, yes. But a Child instance could also be added to a `list` later too. Something else: a Child instance is an attribute of a Parent instance, which itself is an attribute of a GrandParent instance, etc. – Basj May 26 '20 at 23:31
  • Does this answer your question? [How to use inspect to get the caller's info from callee in Python?](https://stackoverflow.com/questions/3711184/how-to-use-inspect-to-get-the-callers-info-from-callee-in-python) – norok2 May 26 '20 at 23:34
  • You could get complicated with this and utilise something like the inspect module, but "Explicit is better than implicit." strongly comes to mind here. – Robert Kearns May 26 '20 at 23:36
  • @norok2 This is related (see Samwise's answer which is interesting), but a solution with `inspect` seems to be finally worse than having to pass `self` manually. I was looking for something simpler, less complicated, but it probably does not exist. – Basj May 26 '20 at 23:38
  • Most definitely. I think using `inspect` for this is something you *can* do, not something you *should* do. But I do not see a way around it. – norok2 May 26 '20 at 23:41
  • @norok2 I was hoping some `__super__` magic solution or something similar, but it's probably out of topic here. – Basj May 26 '20 at 23:44
  • @Basj You could have used `__super__` if you really had parent/child relationship. As of your code the two classes are really unrelated except that one instantiate one object of the other. If it were `class Child(Parent): ...` then a *super*-based approach could have worked. – norok2 May 26 '20 at 23:47

2 Answers2

1

Technically you can do this by walking the stack with the inspect module, something like this:

>>> import inspect
>>>
>>> class Parent:
...     def __init__(self):
...         self.child = Child()
...
>>> class Child:
...     def __init__(self):
...         for frame in inspect.stack():
...             if isinstance(frame[0].f_locals.get('self'), Parent):
...                 self.parent = frame[0].f_locals.get('self')
...                 break
...
>>> p = Parent()
>>>
>>> p.child.parent
<__main__.Parent object at 0x000001ED3901A310>

This is terrible for many reasons though. Ideally you should avoid having circular references like this at all, never mind building them by rummaging around in the call stack.

Samwise
  • 68,105
  • 3
  • 30
  • 44
1

Instead of using inspect to dynamically determine the caller, you could create a setup method for instantiating the children.

class Parent:
    def __init__(self):
        self.child = self.create_child(Child)

    def create_child(self, cls, *args, **kwargs):
        return cls(*args, parent=self, **kwargs)

If this is something you are likely to use in many classes, you could move it into a base class as well.

Robert Kearns
  • 1,631
  • 1
  • 8
  • 15
  • Sorry that was a typo, just fixed it – Robert Kearns May 26 '20 at 23:47
  • I think this is the best solution! Would it also work if I need to pass another parameters to create the Child instance, e.g. `Child(x=1, y=2, z=3)`. Probably with *arg **kwarg? How would it look like? – Basj May 26 '20 at 23:49
  • Yes exactly, you could allow any arbitrary amount of arguments with star notation. I will update the answer. – Robert Kearns May 26 '20 at 23:53