Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Very strange __getattr__ behavior in python

I had an error in my code (similar to the error below) but the result seems very strange to me. Can someone explain to me how getattr is getting called with the name being "hello"?

class A(object):
    def __getattr__(self, name):
        print name
        assert name != 'hello'
        return self.hello

class C(object):
    def __init__(self):
        class B(A):
            @property
            def hello(self_):
                return self.durp

        self.b = B()

c = C()
print c.b.bang

The output of this is:

bang
hello
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    print c.b.bang
  File "test.py", line 5, in __getattr__
    return self.hello
  File "test.py", line 4, in __getattr__
    assert name != 'hello'
AssertionError

shell returned 1

I understand of course that there is no durp property in C (this is the error I speak of). However, I would not expect __getattr__ to be called with hello as the string. I feel like I'm not understanding something fundamental about the way __getattr__ works.

like image 393
CrazyCasta Avatar asked Mar 26 '26 13:03

CrazyCasta


1 Answers

__getattr__ is called when accessing an attribute via "the usual methods" fails. The way Python knows whether "the usual methods" have failed is that an AttributeError is raised. It can't tell, though, whether this AttributeError was raised in trying to get the original attribute you were trying to get, or some other attribute accessed as part of that process. So here's what happens:

  1. Python tries to access c.b.bang
  2. the b object has no attribute "bang" so the __getattr__ is called.
  3. the __getattr__ tries to get self.hello
  4. Python finds the attribute hello, whose value is a property
  5. In trying to evaluate self.hello, Python runs the hello property.
  6. Running the hello property tries to access a nonexistent attribute durp. This raises an AttributeError
  7. Because an AttributeError was raised as part of trying to evaluate self.hello, Python thinks there is no such attribute available via "the usual methods". It therefore calls __getattr__ with "hello" as the argument.

This behavior is confusing, but is basically an unavoidable side effect of how attribute lookup in Python works. The only way to know that "normal attribute lookup didn't work" is that an AttributeError was raised. There is a Python bug about this, which apparently people are still working on, but no real solution exists so far.

like image 95
BrenBarn Avatar answered Mar 29 '26 02:03

BrenBarn