I'm trying to build a system where a base class is used for every other object. Every base object has a _fields dictionary internally where implementations of the base class can store their information.
Base class implementation is quite simple:
class A(object):
def __init__(self, fields=dict()):
self._fields = fields
An implementation of the class can set the field in the __init__ call to its super().
What I'd like to add is that the fields are accessible as properties without having to add the @property decorator to a whole bunch of functions. I've overriden the __getattr__ for this purpose in the base class like so:
class A(object):
def __init__(self, fields=dict()):
self._fields = fields
def __getattr__(self, name):
if hasattr(self, name):
return object.__getattribute__(self, name)
elif name in self._fields:
return self._fields.get(name)
else:
raise AttributeError
Now an implementation for this class can work like this:
class A_impl(A):
def __init__(self):
super(A_impl, self).__init__(
fields=dict(
val_1="foo",
val_2="",
val_3="bar",
)
)
By which creating an implementation of this class gives you the options to do:
test = A_imp()
print test.val_1
print test.val_2
print test.val_3
Which returns
foo
bar
I can even override this via @property decorators, changing the class like so:
class A_impl(A):
def __init__(self):
super(A_impl, self).__init__(
fields=dict(
val_1="foo",
val_2="",
val_3="bar",
)
)
@property
def val_1(self):
return self._fields.get('val_1') + "_getter"
Which allows me to manipulate the data for return. The only issue is that if I want to be able to set one of these field variables I have to implement the descriptor setter functions which also requires me to make the property descriptor which creates a lot of duplicate work (ie I have to define descriptors for ALL my fields which is what I want to avoid here.
I implemented the __setattr__ function for the base class to solve the issue where if I manually implement a descriptor setter function it should be chosen over the default which is self._field[name] = value. The base class now looks like this (similar to the __getattr__):
class A(object):
def __init__(self, fields=dict()):
self._fields = fields
def __getattr__(self, name):
if hasattr(self, name):
return object.__getattribute__(self, name)
elif name in self._fields:
return self._fields.get(name)
else:
raise AttributeError
def __setattr__(self, name, value):
if hasattr(self, name):
object.__setattr__(self, name, value)
elif name in self._fields:
self._fields[name] = value
else:
raise AttributeError
Now if I run the same code test again:
test = A_imp()
print test.val_1
print test.val_2
print test.val_3
It instantly gets stuck in an infinite loop, it starts at the __setattr__ but jumps into the __getattr__ right after and keeps looping that.
I've been reading a lot of questions on stackoverflow for this and couldn't figure it out, this is why I build this test case to cleanly figure it out. Hopefully someone's able to clarify this for me and help me solve the issue.
There is no way to check whether an object has an attribute other than actually trying to retrieve it. Thus,
actually tries to access
self.val_1. Ifself.val_1isn't found through the normal means, it falls back on__getattr__, which callshasattragain in an infinite recursion.hasattractually catches any exception, includingRuntimeError: maximum recursion depth exceeded, and returnsFalse, so the exact way this manifests depends on precisely how many__getattr__calls are nested. Some__getattr__calls hit theobject.__getattribute__case and raise anAttributeError; some hit theelif name in self._fields: return self._fields.get(name)case. Ifself._fieldsexists, this returns a value, but with your__setattr__, sometimesself._fieldsdoesn't exist!When your
__setattr__tries to handle theself._fieldsassignment in__init__, it callshasattr(self, '_fields'), which calls__getattr__. Now some__getattr__calls make two recursive__getattr__calls, one inhasattrand one inelif name in self._fields. Sincehasattris catching exceptions, this causes recursive calls exponential in the recursion depth instead of quickly either seeming to work or raising an exception.Don't use
hasattrin__getattr__or__getattribute__. In general, be very careful about all attempts to access attributes inside the methods that handle attribute access.