How to use __setattr__ correctly, avoiding infinite recursion

PythonPython 2.7GetattrSetattr

Python 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexView Question on Stackoverflow
Solution 1 - PythonBakuriuView Answer on Stackoverflow
Solution 2 - PythonVindolinView Answer on Stackoverflow
Solution 3 - PythonDanny HpyView Answer on Stackoverflow
Solution 4 - PythonAviad RozenhekView Answer on Stackoverflow
Solution 5 - PythonFaxView Answer on Stackoverflow