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

Is now available as a print book: Amazon

pythoncover


Contents

  1. Hello Python World
  2. Variables, Objects and Attributes
  3. The Function Object
      Extract - Function Objects
  4. Scope, Lifetime and Closure
      Extract - Local and Global
  5. Advanced Functions
      Extract -  Decorators 
  6. Class Methods and Constructors
  7. Inside Class
  8. Metaclass
  9. Advanced Attributes
      Extract - Properties
  10. Custom Attribute Access
      Extract -  Custom Attributes
      Extract -  Default Methods ***NEW
  11. Single Inheritance
  12. Multiple Inheritance
  13. Class and Type
      Extract - Class
  14. Type Annotation
      Extract - Type Annotation 
  15. More Magic - Operator Overloading

 

Advanced Attributes

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

square

 



 

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 )