25

When i say "python attribute lookup proccess" i mean: what python does when you write x.foo??

Searching the web i didn't found to much docs about this, one of the best papers i found resumed the proccess to the following steps (you can see the full article here)

  1. If attrname is a special (i.e. Python-provided) attribute for objectname, return it.
  2. Check objectname.__class__.__dict__ for attrname. If it exists and is a data-descriptor, return the descriptor result. Search all bases of objectname.__class__ for the same case.
  3. Check objectname.__dict__ for attrname, and return if found. If objectname is a class, search its bases too. If it is a class and a descriptor exists in it or its bases, return the descriptor result.
  4. Check objectname.__class__.__dict__ for attrname. If it exists and is a non-data descriptor, return the descriptor result. If it exists, and is not a descriptor, just return it. If it exists and is a data descriptor, we shouldn't be here because we would have returned at point 2. Search all bases of objectname.__class__ for same case.
  5. Raise AttributeError.

At first this might seem right, but the attribute lookup process is a little bit more complicated, for example for x.foo, it doesn't behave the same if x is a class or an instance.

I have a found some samples that can't be explained by this way. Consider the following python code:

class Meta(type):
    def __getattribute__(self, name):
        print("Metaclass getattribute invoked:", self)
        return type.__getattribute__(self, name)

    def __getattr__(self, item):
        print('Metaclass getattr invoked: ', item)
        return None

class C(object, metaclass=Meta):
    def __getattribute__(self, name):
        print("Class getattribute invoked:", args)
        return object.__getattribute__(self, name)

c=C()

Now check the following lines with the corresponding output:

>> C.__new__
Metaclass getattribute invoked: <class '__main__.C'>
<built-in method __new__ of type object at 0x1E1B80B0>

>> C.__getattribute__
Metaclass getattribute invoked: <class '__main__.C'>
<function __getattribute__ at 0x01457F18>

>> C.xyz
Metaclass getattribute invoked: <class '__main__.C'>
Metaclass getattr invoked:  xyz
None

>> c.__new__
Class getattribute invoked: (<__main__.C object at 0x013E7550>, '__new__')
<built-in method __new__ of type object at 0x1E1B80B0>

>> c.__getattribute__
Class getattribute invoked: (<__main__.C object at 0x01438DB0>, '__getattribute__')
Metaclass getattribute invoked: <class '__main__.C'>
<bound method C.__getattribute__ of <__main__.C object at 0x01438DB0>>

>> 

The conclusions i have been are (considering we're searching for x.foo):

  • __getattribute__ is different for instances of < type 'type' > and < type 'object' >. For C.foo(), 'foo' is searched first on C.__dict__ and returned if found (instead of searching type(C)) and for x.foo() 'foo' is searched on type(x).__dict__ and on x.__dict__.
  • __getattribute__ method is always resolved on type(x), what i don't understand here is the last case: c.__getattribute__, isn't object contains a method __getattribute__ (and C inherits from object), so why does metaclass getattribute method gets called.

Can someone explain this please?? or at less tell me where can i find some documentation about this, thanks.

Ariel
  • 1,615
  • 1
  • 18
  • 27

1 Answers1

4

If you added print("Metaclass getattribute invoked:", self, name) you'd see:

>>> c.__getattribute__
Class getattribute invoked: <__main__.C object at 0x2acdbb1430d0> __getattribute__
Metaclass getattribute invoked: <class '__main__.C'> __name__
<bound method C.__getattribute__ of <__main__.C object at 0x2acdbb1430d0>>

The metaclass __getattribute__ is getting invoked in order to build the repr of the expression c.__getattribute__, so that it can print C's __name__.

btw, __getattribute__ works the same for classes and metaclasses; the attribute is looked up first on the instance then on the instance's type.

>>> Meta.foo = 1
>>> C.foo
('Metaclass getattribute invoked:', <class '__main__.C'>, 'foo')
1
>>> c.foo
('Class getattribute invoked:', <__main__.C object at 0x2acdbb1430d0>, 'foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getattribute__
AttributeError: 'C' object has no attribute 'foo'
>>> C.bar = 2
>>> c.bar
('Class getattribute invoked:', <__main__.C object at 0x2acdbb1430d0>, 'bar')
2
ecatmur
  • 137,771
  • 23
  • 263
  • 343