Esteemed colleagues!
Please consider...
#!/usr/bin/env python3.6
class Child(object):
def __init__(self, name:str, value = 0):
self.name = name;
self.value = value;
def __repr__(self):
return ' child "{}" has value {}\n'.format(self.name, self.value)
class Parent(object):
def __init__(self, name:str):
self.name = name
self.children = {}
def __repr__(self):
s = 'Parent "{}":\n'.format(self.name)
for k, v in self.children.items():
s = v.__repr__()
return s
def __getattr__(self, name:str):
if name == 'children':
return self.children;
elif name in self.children.keys():
return self.children[name].value
else:
return super().__getattr__(name)
def __setattr__(self, prop, val):
if prop == 'name':
super().__setattr__(prop, val)
else:
super().__setattr__('children[{}]'.format(prop), val)
p = Parent('Tango')
p.children['Alfa'] = Child('Alfa', 55)
p.children['Bravo'] = Child('Bravo', 66)
print(p)
print(p.children['Alfa']) # Returns '55' (normal)
print(p.Alfa) # Returns '55' (__getattr__)
print('-----')
p.Alfa = 99 # This is creating a new variable, need __setattr__ ...
print(p.Alfa) # Prints new variable. __setattr__ is not called!
print('-----')
print(p.children['Alfa']) # Still '55'
print(p) # Still '55'
The intent of the code is to allow those holding a Parent to access its children in TWO ways: p.children['Alfa'] or p.Alfa.
Using __getattr__() I can accomplish the read-side of this. (Comment out the def __setattr__() in the code above and you can see the read-side work as expected.) Output without setattr() is:
Parent "Tango":
child "Alfa" has value 55
child "Bravo" has value 66
child "Alfa" has value 55
55
-----
99
-----
child "Alfa" has value 55
Parent "Tango":
child "Alfa" has value 55
child "Bravo" has value 66
Of course now I need __setattr__() to accomplish the write-side of this. I want 99 to be assigned to Alfa. As of now, haven't figured out the incantation to avoid a variety of issues.
The code above with setattr() raises RecursionError:
Traceback (most recent call last):
File "./test.py", line 37, in <module>
p.children['Alfa'] = Child('Alfa', 55)
File "./test.py", line 23, in __getattr__
return self.children;
File "./test.py", line 23, in __getattr__
return self.children;
File "./test.py", line 23, in __getattr__
return self.children;
[Previous line repeated 328 more times]
File "./test.py", line 22, in __getattr__
if name == 'children':
RecursionError: maximum recursion depth exceeded in comparison
I expect the test code that follows to show that by writing to p.Alfa, that I am actually updating the value of self.children['Alfa']. After the assignment, 99 should appear when I print it, not the original 55.
Note that in the real world, there is a nearly infinite number of possible children, their names, and their contents.
Your help and insight is appreciated!
CodePudding user response:
def __setattr__(self, attr, val):
if attr == 'name':
super().__setattr__(self, 'name', val)
return
self.children[attr] = val
Your code never initializes an attribute named children. It initializes one named children[children]. As a result, when you later try to assign to self.children['Alfa'], the first thing it tries to do is find an attribute named children. When it isn't found, it calls __getattr__, which says that when name == "children", it should return self.children, and the infinite loop begins.
__getattr__ is only called when an attribute is not found via the normal process. With __setattr__ defined correctly, __getattr__ should never be called for children, since you define it in __init__. The definition can be reduced to
def __getattr__(self, name:str):
if name in self.children.keys():
return self.children[name].value
else:
return super().__getattr__(name)
