How to use __setattr__ correctly, avoiding infinite recursion
PythonPython 2.7GetattrSetattrPython Problem Overview
I want to define a class containing read
and write
methods, which can be called as follows:
instance.read
instance.write
instance.device.read
instance.device.write
To not use interlaced classes, my idea was to overwrite the __getattr__
and __setattr__
methods and to check, if the given name is device
to redirect the return to self
. But I encountered a problem giving infinite recursions. The example code is as follows:
class MyTest(object):
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
if name=="device":
print "device test"
else:
setattr(self, name, value)
test = MyTest(1)
As in __init__
the code tried to create a new attribute x
, it calls __setattr__
, which again calls __setattr__
and so on. How do I need to change this code, that, in this case, a new attribute x
of self
is created, holding the value 1
?
Or is there any better way to handle calls like instance.device.read
to be 'mapped' to instance.read
?
As there are always questions about the why: I need to create abstractions of xmlrpc
calls, for which very easy methods like myxmlrpc.instance,device.read
and similar can be created. I need to 'mock' this up to mimic such multi-dot-method calls.
Python Solutions
Solution 1 - Python
You must call the parent class __setattr__
method:
class MyTest(object):
def __init__(self, x):
self.x = x
def __setattr__(self, name, value):
if name=="device":
print "device test"
else:
super(MyTest, self).__setattr__(name, value)
# in python3+ you can omit the arguments to super:
#super().__setattr__(name, value)
Regarding the best-practice, since you plan to use this via xml-rpc
I think this is probably better done inside the _dispatch
method.
A quick and dirty way is to simply do:
class My(object):
def __init__(self):
self.device = self
Solution 2 - Python
Or you can modify self.__dict__
from inside __setattr__()
:
class SomeClass(object):
def __setattr__(self, name, value):
print(name, value)
self.__dict__[name] = value
def __init__(self, attr1, attr2):
self.attr1 = attr1
self.attr2 = attr2
sc = SomeClass(attr1=1, attr2=2)
sc.attr1 = 3
Solution 3 - Python
You can also use object.
class TestClass:
def __init__(self):
self.data = 'data'
def __setattr__(self, name, value):
print("Attempt to edit the attribute %s" %(name))
object.__setattr__(self, name, value)
Solution 4 - Python
or you can just use @property:
class MyTest(object):
def __init__(self, x):
self.x = x
@property
def device(self):
return self
Solution 5 - Python
If you don't want to specify which attributes can or cannot be set, you can split the class to delay the get/set hooks until after initialization:
class MyTest(object):
def __init__(self, x):
self.x = x
self.__class__ = _MyTestWithHooks
class _MyTestWithHooks(MyTest):
def __setattr__(self, name, value):
...
def __getattr__(self, name):
...
if __name__ == '__main__':
a = MyTest(12)
...
As noted in the code you'll want to instantiate MyTest, since instantiating _MyTestWithHooks will result in the same infinite recursion problem as before.