|Programmer's Python - Custom Attributes|
|Written by Mike James|
|Monday, 21 October 2019|
Page 2 of 2
Custom Attribute Access
Descriptor objects allow you to create attributes that have custom behavior, but Python goes one step further by exposing the mechanism by which attributes are located. It is this mechanism that is responsible for implementing descriptors, and it is at the heart of how attributes are accessed.
There are two magic methods that are involved in getting attributes – __getattr__ and __getattribute__.
Of the two it is __getattribute__ that is fundamental as it is called for every attribute access and if you override it then you override every attribute access.
Note that many magic methods are accessed directly and __getattribute__ is not involved in the access so you cannot customize it. There is a limit to even Python’s magic.
class MyClass: def __getattribute__(self, item): print(item) return None
In this case __getattribute__ simply prints the name of the attribute and returns None. This may be trivial but already you can see the potential as now any instance that you create seems to have any attribute you care to access:
myObj=MyClass() myObj.myAttribute1 myObj.myAttribute2 myObj.myAttribute3
prints the name of each attribute and doesn’t throw an exception that myObj doesn’t have these attributes.
If you try the same thing with the class:
then you will see an exception telling you that MyClass doesn’t have myAttribute1.
The class supplies the __getattribute__ method to its instances and the metaclass supplies the __getattribute__ method to the class.
class MyMeta(type): def __getattribute__(self, item): print(item) return None class MyClass(metaclass=MyMeta): def __getattribute__(self, item): print(item) return None
After this you can access any attribute on MyClass just as you did in the instance.
The idea behind __getattribute__ is fairly simply but there is one trap that you have to be aware of.
When you define a custom __getattribute__ it is called for every attribute access – even for the attributes you don’t want to modify the access to.
In addition, if you try to access an attribute from within __getattribute__ then __getattribute__ is called and the result is an infinite recursion.
If you want to access attributes within __getattribute__ you have to call the __getattribute__ method of the superclass.
For example, if you write:
class MyClass: myAttribute1=42 def __getattribute__(self, item): print(item) return self.myAttribute1
Then the final return self.myAttribute1 will result in __getattribute__ being called again to supply the result, and this in turn will call __getattribute__ again and so on until the system runs out of memory.
The correct approach is:
class MyClass: myAttribute1=42 def __getattribute__(self, item): print(item) return super().__getattribute__(item)
Now the base class’s __getattribute__ will return the attribute without involving out custom __getattribute__.
It is worth knowing that the built-in function:
also calls __getattribute__
Included in chapter but not in this extract:
|Last Updated ( Thursday, 23 April 2020 )|