Programmer's Python - Custom Attributes
Written by Mike James   
Monday, 21 October 2019
Article Index
Programmer's Python - Custom Attributes
Custom Attribute Access

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.

__getattribute__

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.

For example:

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:

MyClass.myAttribute1

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.

For example:

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:

getattr(object,item,value)

also calls __getattribute__

Included in chapter but not in this extract:

  • __getattr__
  • Customizing Assignment __setattr__
  • Default Methods
  • Custom Deleting Attributes
  • Custom Dir
  • Slots

Summary

  • The descriptor object can customize access to any attribute via the __get__, __set__ and __delete__ functions which are automatically called on attribute access or deletion.

  • If a descriptor defines __get__ and __set__ then it is a data descriptor; if only __get__ is defined it is a non-data descriptor.

  • Data descriptors cannot be overridden by a definition in the instance, whereas non-data descriptors can be.

  • __getattribute__ is called for every attribute access and if you override it then you override every attribute access.

  • If __getattribute__ fails to find an attribute then it automatically calls __getattr__ if it is defined.

  • __setattr__ is called every time you attempt an assignment to an attribute.

  • You can use __getattr__ to implement default attributes and methods.

  • You can use del or delattr to delete an attribute but in either case __delattr__ is called if it is defined.

  • The __dir__ magic method can be used to define a custom dir command.

  • Slots are just a more efficient way of implementing attributes using a descriptor.

  • Another way of accessing attributes is via the index or key operator []. This can be customized using __getitem__ and __setitem__. There are also a range of additional magic methods that can make attribute access look more like a collection.

Programmer's Python
Everything is an Object
Second Edition

Is now available as a print book: Amazon

pythonObject2e360

Contents

  1. Get Ready For The Python Difference
  2. Variables, Objects and Attributes
  3. The Function Object
  4. Scope, Lifetime and Closure
      Extract 1: Local and Global ***NEW!
  5. Advanced Functions
  6. Decorators
  7. Class, Methods and Constructors
      Extract 1: Objects Become Classes 
  8. Inside Class
  9. Meeting Metaclasses
  10. Advanced Attributes
  11. Custom Attribute Access
  12. Single Inheritance
  13. Multiple Inheritance
  14. Class and Type
  15. Type Annotation
  16. Operator Overloading
  17. Python In Visual Studio Code

 Extracts from the first edition

<ASIN:1871962749>

<ASIN:1871962595>

<ASIN:1871962765>

Related Articles

Creating The Python UI With Tkinter

Creating The Python UI With Tkinter - The Canvas Widget

The Python Dictionary

Arrays in Python

Advanced Python Arrays - Introducing NumPy

raspberry pi books

 

Comments




or email your comment to: comments@i-programmer.info

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner

<ASIN:1871962587>

 



Last Updated ( Thursday, 23 April 2020 )