Search code examples
pythongetattr

Python: why visiting an existing attribute triggered "__getattr__"?


I knew that python's __getattr__ is triggered when visiting a non-existing attribute.

But in my example below, inside c1's __init__, I created a self attribute called name. When visiting it, both access ways triggered __getattr__ and thus printed "None".

This is weird to me. I suppose either my understanding or my code has some issue?

$ cat n.py

class c1(object):
    def __init__(s):
        print 'init c1'
        s.name='abc'
    def __getattr__(s,name):
        print "__getattr__:"+name
        return None
    def __setattr__(s,name,value):
        print "__setattr__:"+value
    def __get__(s,inst,owner):
        print "__get__"

class d:
    def __init__(s):
        s.c=c1()

c=c1()
print c.name
o=d()
print o.c.name

$ python n.py

init c1
__setattr__:abc
__getattr__:name
None
init c1
__setattr__:abc
__getattr__:name
None

You can see I've defined s.name='abc' inside __init__, but it is not recognized when calling it.


Solution

  • You also implemented __setattr__, and it is always called when trying to set an attribute. Your version only prints the attribute:

    def __setattr__(s,name,value):
        print "__setattr__:"+value
    

    and nothing else, which means the attribute is not actually set. The above is called for the s.name='abc' expression, the name attribute is never set, so any future access to the name attribute is sent to __getattr__ again.

    Have __setattr__ set the value in __dict__ directly:

    def __setattr__(self, name, value):
        print "__setattr__:" + value
        self.__dict__[name] = value
    

    or make your class a new-style class (inherit from object), and you can re-use the base implementation with super(c1, self).__setattr__(name, value).

    As a side note: you implemented c1.__get__, presumably in an attempt to make the class a descriptor object. However, the descriptor protocol only applies to class attributes, not to instance attributes, and then only on new-style classes. Your class d is not a new-style class, and you used an instance attribute c to store the c1 instance.