Search code examples
pythonclassgetattr

Why is __getattr__catching also existing attributes?


I created this class to test some of the __getattr__ features:

class Trace:
    def __init__(self, val):
        self.val = val

    def __setattr__(self, attr, value):
        print('set ' + attr)

    def __getattr__(self, attr):
        print('get ' + attr)

I then created an instance

a = Trace(10)
print(a.val)
a.val = 5
print(a.val)

But, even if I fetched only existing attributes, this was the output:

set val
get val
None
set val
get val
None

I'm using Python 3.7.


Solution

  • __getattr__ is only called when an attribute isn’t found normally, but __setattr__ has no such restriction (that’s why there’s no equivalent of __getattribute__ for it). You overrode __setattr__ to not actually set an instance attribute, so the attribute is always missing and __getattr__ is called anyway. (Yes, it applies in __init__ too.)

    Remove __setattr__ to see your expected behaviour quickly, or add its default behaviour back:

    def __setattr__(self, attr, value):
        print('set ' + attr)
        super().__setattr__(attr, value)