How to use __setattr__ correctly, avoiding infinite recursion

73,532

Solution 1

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

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

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

or you can just use @property:

class MyTest(object):

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

    @property
    def device(self):
        return self

Solution 5

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.

Share:
73,532
Alex
Author by

Alex

Updated on July 05, 2022

Comments

  • Alex
    Alex almost 2 years

    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.

  • Alex
    Alex about 11 years
    I like the quick-and-dirty way: Just one line to get the desired behaviour! Thanks
  • djvg
    djvg over 5 years
    This is also mentioned in the Python docs.
  • Maxpm
    Maxpm about 5 years
    Why does this not call setattr(self, "__dict__", value), repeating the original infinite recursion problem?
  • Se Norm
    Se Norm about 5 years
    Because this is not setting the __dict__ attribute, gut getting it and then setting an item on the returned dict, i.e., it's calling sth like: getattr(self, "__dict__").__setitem__(name, value)
  • Admin
    Admin almost 5 years
    This a builtin class. All classes inherits from this class (even __setattr__, __getattr__, __dict__, ...)
  • thomas.mac
    thomas.mac almost 5 years
    similar to type then ?
  • Admin
    Admin almost 5 years
    It's not fully similar, type(TestClass) == object or type == object returns False. Note that everything has the __setattr__ method even a string: "a".__setattr__ because all classes inherits from the builtin class object even strings
  • Shahryar
    Shahryar about 4 years
    @Bakuriu would you please explain what this line is doing exactly? super(MyTest, self).__setattr__(name, value)
  • Bakuriu
    Bakuriu about 4 years
    @Shahryar It is explained in the first line of my answer. It is calling the parent class __setattr__ method. super(Class, instance) creates an object that is able to call the methods of the parent class of Class on instance. You could also do object.__setattr__(instance, name, value) in this case but super is able to handle correctly multi-inheritance and should be preferred.
  • Shahryar
    Shahryar about 4 years
    why can't we simply say self[name] = value? is it wrong?
  • Bakuriu
    Bakuriu about 4 years
    @Shahryar Because that's not even valid python syntax. self[name] = value is equivalent to self.__setitem__(name, value) which most of the time is not implemented. And if you try to use the setattr builtin what would happen is that setattr(self, name value) will recursively call __setattr__ and you end up in an infinite loop.