How to use __setattr__ correctly, avoiding infinite recursion

Question:

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.

Asked By: Alex

||

Answers:

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
Answered By: Bakuriu

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
Answered By: Vindolin

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)
Answered By: Danny Hpy

or you can just use @property:

class MyTest(object):

    def __init__(self, x):
        self.x = x

    @property
    def device(self):
        return self
Answered By: Aviad Rozenhek

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.

Answered By: Fax

Use super function

class foo:
    def __setattr__(self,name,value):
        super(type(self),self)
        self.__dict__[name]=value

Command super(type(self),self) will make self the class the contains all variables of current instance but you can acces and change all of them.
__dict__ contains all the variables declared in the current instance

Answered By: Supergamer
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.