Search code examples
pythonpython-descriptors

Why does property override object.__getattribute__?


I noticed that contrary to the classmethod and staticmethod decorators, the property decorator overrides the object.__getattribute__ method:

>>> list(vars(classmethod))
['__new__', '__repr__', '__get__', '__init__', '__func__', '__wrapped__', '__isabstractmethod__', '__dict__', '__doc__']
>>> list(vars(staticmethod))
['__new__', '__repr__', '__call__', '__get__', '__init__', '__func__', '__wrapped__', '__isabstractmethod__', '__dict__', '__doc__']
>>> list(vars(property))
['__new__', '__getattribute__', '__get__', '__set__', '__delete__', '__init__', 'getter', 'setter', 'deleter', '__set_name__', 'fget', 'fset', 'fdel', '__doc__', '__isabstractmethod__']

The functionality of the property decorator doesn’t seem to require this override (cf. the equivalent Python code in the Descriptor HowTo Guide). So which behaviour exactly does this override implement? Please provide a link to the corresponding C code in the CPython repository, and optionally an equivalent Python code.


Solution

  • To refresh yourself on what __getattribute__ is and how this is implemented in CPython, please refer to this very excellent answer first, as that answers contains all the detailed background information on what to look for in the CPython source, and the paragraphs below will reference those details without further explanations.

    In CPython, all builtin types (e.g. object, int, str, float) all follow a standard way on how they are defined. These builtin types with their dunder methods are all slotted in the underlying C implementation, for example float simply reference the default PyObject_GenericGetAttr for tp_getattro, and this is exactly the same for property. Essentially, the descriptor howto guide is correct in omitting any references to __getattribute__, as its presence is nothing more than an artifact on how builtin types in Python are defined (i.e. they all point to the same generic implementation for "the normal way of looking for object attributes" if it's just a pointer to the aptly named PyObject_GenericGetAttr).

    As for why classmethod does not have __getattribute__ appear as part of its vars output, that's because the definition for classmethod does not include tp_getattro nor tp_getattr, but it just means that its __getattribute__ falls back to its parent's, which is object.__getattribute__ (check in Python for output of classmethod.__getattribute__ is object.__getattribute__). As for why classmethod is defined without the explicit linkage while various other builtin types define an explicit linkage to PyObject_GenericGetAttr (despite the outcome of either decisions being functionally the same in nearly all code usage), you may have to ask the developers themselves.

    Addendum: though at some point 12 years ago (2012), around the time of Python 3.2 was being developed, classmethod did have tp_getattro set, but subsequent to that commit 2cf936fe7a55 (included since 2.7.5 and 3.3.0a1) - the commit message has no hints as to why it was simply changed to "use defaults". As for float and property type, the change was introduced way back in the merge commit 6d6c1a35e08b from nearly 22 years ago (2001) that brought in the changes outlined in PEP-252 which is the descriptor protocol itself. Note that it set all tp_getattro to PyObject_GenericGetAttr for standard types that didn't have it defined before (and likewise for classmethod which is where this type made its debut).