Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is `getattr` related to `object.__getattribute__` and to `object.__getattr__`?

From https://docs.python.org/3.6/library/functions.html#getattr

getattr(object, name[, default])

Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.

How is getattr related to object.__getattribute__ and to object.__getattr__?

Does getattr call object.__getattribute__ or object.__getattr__? I would guess the former?

like image 515
Tim Avatar asked Jul 08 '17 23:07

Tim


1 Answers

Summary Answer

In general, a dotted lookup invokes __getattribute__.

If the code in __getattribute__ doesn't find the attribute, it looks to see if __getattr__ is defined. If so, it is called. Otherwise, AttributeError is raised.

The getattr() function is just an alternative way to call the above methods. For example getattr(a, 'x') is equivalent to a.x.

The getattr() function is mainly useful when you don't know the name of an attribute in advance (i.e. when it is stored in a variable). For example, k = 'x'; getattr(a, k) is equivalent to a.x.

Analogy and high level point-of-view

The best way to think of it is that __getattribute__ is the first called primary method and __getattr__ is the fallback which is called when attributes are missing. In this way, it is very much like the relationship between __getitem__ and __missing__ for square bracket lookups in dictionaries.

Demonstration code

Here is a worked-out example:

>>> class A(object):
    x = 10
    def __getattribute__(self, attr):
        print(f'Looking up {attr!r}')
        return object.__getattribute__(self, attr)
    def __getattr__(self, attr):
        print(f'Invoked the fallback method for missing {attr!r}')
        return 42

>>> a = A()
>>> a.x
Looking up 'x'
10
>>> a.y
Looking up 'y'
Invoked the fallback method for missing 'y'
42

>>> # Equivalent calls with getattr()
>>> getattr(a, 'x')
Looking up 'x'
10
>>> getattr(a, 'y')
Looking up 'y'
Invoked the fallback method for missing 'y'
42

Official Documentation

Here are the relevant parts of the docs:

object.__getattr__(self, name) Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

Note that if the attribute is found through the normal mechanism, __getattr__() is not called. (This is an intentional asymmetry between __getattr__() and __setattr__().) This is done both for efficiency reasons and because otherwise __getattr__() would have no way to access other attributes of the instance. Note that at least for instance variables, you can fake total control by not inserting any values in the instance attribute dictionary (but instead inserting them in another object). See the __getattribute__() method below for a way to actually get total control over attribute access.

object.__getattribute__(self, name) Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError. This method should return the (computed) attribute value or raise an AttributeError exception. In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).

like image 170
Raymond Hettinger Avatar answered Sep 24 '22 19:09

Raymond Hettinger