Piotr Kowalczyk
Piotr Kowalczyk

Reputation: 159

Why using __dict__ in __setattr__ causing infinite loop in __getattr__

I wrote a working program for this specified simplified Mesh class, but I can not make it work for real class with dozens of methods/properties. I can not modify real Mesh class, and I can not make Object class extended Mesh.

This works fine:


    class Mesh:
        def __init__(self):
            self.hide_render = False


    class Object:
        def __init__(self, mesh_):
            self.mesh = mesh_

        def __getattr__(self, item):
            return self.mesh.__getattribute__(item) # infinite loop in this line

        def __setattr__(self, name, value):
            if name == 'hide_render': # line to replace----------
                self.mesh.__setattr__(name, value)
            else:
                super().__setattr__(name, value)

    ob = Object(Mesh())

    print(ob.hide_render)
    print(ob.mesh.hide_render)
    ob.mesh.hide_render = True
    print(ob.hide_render)
    print(ob.mesh.hide_render)
    ob.hide_render = False
    print(ob.hide_render)
    print(ob.mesh.hide_render)

Output:

    False
    False
    True
    True
    False
    False

But when I want to do the same for real Mesh class with has much more than "hide _render" attribute by replacing the first line in setattr method with: if name not in self.__dict__: or if name in self.mesh.__dict__:

I get an infinite loop in a getattr method. Why? And how to solve this?

Upvotes: 0

Views: 578

Answers (1)

Blckknght
Blckknght

Reputation: 104822

Your problems occur when self.mesh doesn't exist. If you're trying to defer all lookups for objects that don't yet exist in self.__dict__ to self.mesh, you run into a problem when you can't lookup or assign to self.mesh itself.

There are a few ways you could fix that. You could use self.__dict__['mesh'] or a super().__setattr__ call from __init__ rather than using a direct assignment. Or you could special case the name mesh in __setattr__:

class Object:
    def __init__(self, mesh):
        self.mesh = mesh

    def __getattr__(self, name):
        return getattr(self.mesh, name)

    def __setattr__(self, name, value):
        if name in self.__dict__ or name == 'mesh':   # special case for 'mesh' here!
            super().__setattr__(name, value)
        else:
            setattr(self.mesh, name, value)

Upvotes: 2

Related Questions