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_1
isn't found through the normal means, it falls back on__getattr__
, which callshasattr
again in an infinite recursion.hasattr
actually 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._fields
exists, this returns a value, but with your__setattr__
, sometimesself._fields
doesn't exist!When your
__setattr__
tries to handle theself._fields
assignment in__init__
, it callshasattr(self, '_fields')
, which calls__getattr__
. Now some__getattr__
calls make two recursive__getattr__
calls, one inhasattr
and one inelif name in self._fields
. Sincehasattr
is 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
hasattr
in__getattr__
or__getattribute__
. In general, be very careful about all attempts to access attributes inside the methods that handle attribute access.