Search code examples
pythongetattr

How to implement __getattr__ method without breaking default behaviour in python classic class


I want to add some logging to __getattr__ method in python classic class. It work well normally, but when using operator overloading, something was wrong.

My code follows:

class A:
    def __getattr__(self, item):
        print('attr:'+ item)
    def __getattribute__(self, item):
        print('attribute:' + item)
        return object.__getattribute__(self, item)
    def __add__(self, other):
        return 2

b = A()
a = b + 1
print(a)

in the line a = b + 1, it will run the __getattr__ method, and the value of item will be __coerce__. In this case, I don't know how to handle it.


Solution

  • The trouble you have is that your __getattr__ method is returning None. Since that's a valid attribute value, Python doesn't know that this is just the default return value from a function that doesn't have a return statement.

    If you don't have anything to return from __getattr__, you should probably raise an AttributeError rather than returning None by default. In this specific situation, Python is checking for a __coerce__ method. If there is an AttributeError, it will be suppressed and the interpreter will fall back on other behavior (using __add__). Try something like this:

    def __getattr__(self, item):
        print('attr:', item)
        raise AttributeError("%s object has no attribute named %r" %
                             (self.__class__.__name__, item))
    

    Now, Python's automatic lookup for __coerce__ will fail, but because it fails in the expected way, the exception will be ignored and the __add__ method will be called as you expect.

    One final thing: Old-style classes in Python are thoroughly obsolete (and have been removed from Python 3). You should not use them unless you need to maintain compatibility in very old software that ran on Python versions prior to 2.2 (which was released more than 12 years ago in 2001, so this compatibility issue is becoming a bit ridiculous). For new code that does not have to work with such ancient Python versions, you should use new-style classes everywhere. Because you're using an old-style class, your __getattribute__ method will never be called (only __getattr__).