Having two simple classes; one with only parent
attribute, and one with both parent
and children
attributes.
This means that the one with both parent
and children
inherits from the one with only parent
.
Here's the class with only parent
attribute.
Let's call it Child
since it can only be a child, not a parent.
I'll use a method set_parent()
to make it more clear, but I would use a setter in my actual code.
class Child(object):
def __init__(self, parent=None):
self.__parent = None
self.set_parent(parent)
def set_parent(self, parent):
# Remove self from old parent's children
if self.__parent:
self.__parent.remove_child(self)
# Set new parent
self.__parent = parent
# Add self to new parent's children
if self.__parent:
self.__parent.add_child(self)
The code makes perfect sense and seems to work just fine.
This is, if the Parent
class looks as simple as this:
class Parent(Child):
def __init__(self, parent=None):
super(Parent, self).__init__(parent)
self.__children = []
def add_child(self, child):
if child not in self.__children:
self.__children.append(child)
def remove_child(self, child):
if child in self.__children:
self.__children.remove(child)
However, I want to be able to call my_parent.add_child(my_child)
and have my_child
's parent attribute set to my_parent
while removing my_child
from it's old parent's children.
I can't seem to figure out how to actually design the code, everything I try will turn into an infinite loop between set_parent()
and add_child()
or remove_child()
.
I know this site is not meant for other people to write code for me, but could someone at least give some hints? My brain just can't handle this problem, I've been thinking for 30 minutes straight and haven't gotten anything done. Help appreciated!
This problem, which is called a "Two-Way Association", is described in book "Refactorization: Improving the Design of Existing Code" by Martin Fowler, Kent Beck, and few more authors. The way the problem is solved in the book is by assigning one of the classes a complete control over another class. First, you need to decide which class must be in control here. I believe that in your case the Child should be in control, which is counter-intuitive to how the real world works. Then, you need to allow the controller to access private members of the controlled. In C++ you would solve this by making one of the classes a "friend" of another. In other languages that have true privacy you could make a public accessor method and clearly state in the documentation that it's to be used by one class only. In Python however you're not limited in such way. Consider the following code:
What you're doing is nonsense. Just make them one class and use either
add_child()
andremove_child()
orset_parent()
, but not both.Before going into the problem at hand, there are a couple of things you should fix with coding in python. First of all, you probably don't want to prefix your instance attribute names with a double underscore. Python will mangle the attribute name by prefixing it with the class name, which will likely result in unintended effects.
Next, it seems like you're trying to construct something similar to a tree. For this task, there is no need to have a separate class for children and parents. You should be able to use the same class for both! As you seem to have realized, Python doesn't have a native tree data structure so most people tend to build their own. You can also see some packages people have made for non-native data structures here.
Finally, for a cheeky (and slightly confusing) tree implementation in python check this gist out. There is a more useful version out there that subclasses
defaultdict
, however I can't seem to find it right now.Okay, so down to business. I've consolidated your
Child
andParent
classes into a singleNode
class. You can ignore thename
,__str__
, and__repr__
definitions as I just used those to keep track of what was what.Now to test this and make sure everything works as expected.
Hopefully that solves your problems. I haven't done exhaustive testing on this so let me know if you run into any issues.
parent.add_child(child)
andchild.set_parent(parent)
are (supposed to be) the same operation. Have one of them delegate to the other, or have both delegate to a third method, or just remove one of them. It'll make things much easier to reason about.The quick and dirty way to go about this would be an
_add_child
method that adds a child without touching the child'sparent
attribute. Yourset_parent
could use that to avoid infinite recursion. However, methods like_add_child
or your currentremove_child
are error-prone, because they break the symmetry of the two-way link. One side's idea of what the relationship looks like is temporarily different from the other side, and it's easy to accidentally get the sides out of sync. It'd be cleaner to implementadd_child
andset_parent
in terms of methods that update both sides of the relationship at once.Unsolicited extra advice: Don't use the double underscore prefix. It doesn't prevent code outside the class from accessing the property, and you shouldn't be trying to prevent that in Python anyway. Just use a single underscore, to indicate that it's not part of the class's public API.