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)