brettcannon/desugar

The Python implementation of object.__getattribute__ does not detect descriptors correctly

geryogam opened this issue · 2 comments

From the Python implementation of object.__getattribute__ in your blog article Unravelling attribute access in Python:

class object:
    def __getattribute__(self: Any, attr: str) -> Any:
        # […]
        for base in self_type.mro():
            if attr in base.__dict__:
                type_attr = base.__dict__[attr]
                type_attr_type = type(type_attr)
                # MISSING FOR-LOOP: for attr_base in type_attr_type.mro():
                if "__get__" in type_attr_type.__dict__:
                    descriptor_type_get = type_attr_type.__dict__["__get__"]
                    # Include/descrobject.h:PyDescr_IsData
                    if "__set__" in type_attr_type.__dict__:
                        # Data descriptor.
                        return descriptor_type_get(type_attr, self, self_type)
                    else:
                        break  # Non-data descriptor.
                else:
                    break  # Plain object.
        # […]

So you are correctly looking up the attribute named attr in the types of the mro() of self_type. However you are not looking up the method __get__ in all the types of the mro() of the found attribute’s type but only in the found attribute’s type. In other words, you are ignoring the base types of the found attribute’s type.

Consequently, your Python implementation is failing to behave as expected below by considering foo as a class attribute instead of a non-data descriptor:

>>> class A:
...     def __get__(self, instance, owner):
...         return 'bar'
... 
>>> class B(A):
...     pass
... 
>>> class C:
...     foo = B()
... 
>>> C().foo
'bar'  # Your implementation returns '<__main__.B object at 0x101d26d60>' instead.

To fix this you should also loop on type_attr_type.mro() in your Python implementation (cf. my comment MISSING FOR-LOOP above).

Fixed in 2661c22.

Thanks!