The __getattribute__() method

An even lower-level attribute processing is the __getattribute__() method. The default implementation of this method attempts to locate the value as an existing attribute in the internal __dict__ (or __slots__). If the attribute is not found, this method calls __getattr__() as a fallback. If the value located is a descriptor (refer to the following Creating descriptors section), then it processes the descriptor. Otherwise, the value is simply returned.

By overriding this method, we can perform any of the following kinds of tasks:

  • We can effectively prevent access to attributes. This method, by raising an exception instead of returning a value, can make an attribute more secret than if we were to merely use the leading underscore (_) to mark a name as private to the implementation.
  • We can invent new attributes similarly to how __getattr__() can invent new attributes. In this case, however, we can bypass the default lookup done by the default version of __getattribute__().
  • We can make attributes perform unique and different tasks. This might make the program very difficult to understand or maintain, and it could also be a terrible idea.
  • We can change the way descriptors behave. While technically possible, changing a descriptor's behavior sounds like a terrible idea.

When we implement the __getattribute__() method, it's important to note that there cannot be any internal attribute references in the method's body. If we attempt to get the value for self.name, it will lead to infinite recursion of the __getattribute__() method.

The __getattribute__() method cannot use any simple self.name attribute access; it will lead to infinite recursions.

In order to get attribute values within the __getattribute__() method, we must explicitly refer to the base method defined in a superclass, or the base class object, as shown in the following snippet:

object.__getattribute__(self, name) 

We can use this kind of processing to inject debugging, audit, or security controls into a class definition. We might, for example, write a line to a log when an attribute is accessed in a particularly important class. A sensible security test might limit access to people with defined access controls.

The following example will show a trivial use of __getattribute__() to prevent access to the single leading _ instance variables and methods in a class. We'll do this by raising an AttributeError exception for any of those kinds of names.

Here's the class definition: 

class SuperSecret:

def __init__(self, hidden: Any, exposed: Any) -> None:
self._hidden = hidden
self.exposed = exposed

def __getattribute__(self, item: str):
if (len(item) >= 2 and item[0] == "_"
and item[1] != "_"):
raise AttributeError(item)
return super().__getattribute__(item)

We've overridden __getattribute__() to raise an attribute error on private names only. This will leave Python's internal __ names visible, but any name with a single _ prefix will be concealed. The _hidden attribute will be nearly invisible. The following is an example of an object of this class being used:

>>> x = SuperSecret('onething', 'another')
>>> x.exposed
'another'
>>> x._hidden # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
File "/Users/slott/miniconda3/envs/py37/lib/python3.7/doctest.py", line 1329, in __run
compileflags, 1), test.globs)
File "<doctest __main__.__test__.test_secret[3]>", line 1, in <module>
x._hidden #
File "/Users/slott/Documents/Writing/Python/Mastering OO Python 2e/mastering-oo-python-2e/Chapter_4/ch04_ex4.py", line 132, in __getattribute__
raise AttributeError(item)
AttributeError: _hidden

The object, x, will respond to requests for the exposed attribute, but will raise an exception for any reference to an attribute that begins with _

This does not fully conceal all of the _ names, however. The dir() function will show the existence of the _hidden attribute. To correct this problem, the __dir__() special method must be overridden to also conceal the names beginning with one _.

As general advice, it's rarely necessary to change the implementation of __getattribute__(). The default implementation gives us access to flexible features via property definitions or as changes to __getattr__().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset