...

/

Analysis of Descriptors

Analysis of Descriptors

Learn how Python uses descriptors internally and how to use descriptors in decorators.

We have seen how descriptors work so far and explored some interesting situations in which they contribute to clean design by simplifying their logic and leveraging more compact classes.

By this point, we've learned that by using descriptors, we can achieve cleaner code, abstracting away repeated logic and implementation details. But how do we know our implementation of the descriptors is clean and correct? What makes a good descriptor? Are we using this tool properly or overengineering with it?

Let’s analyze descriptors in order to answer these questions.

How Python uses descriptors internally

What makes a good descriptor? A simple answer would be that a good descriptor is pretty much like any other good Python object. It is consistent with Python itself. The idea that follows this premise is that analyzing how Python uses descriptors will give us a good idea of good implementations so that we know what to expect from the descriptors we write.

We will see the most common scenarios where Python itself uses descriptors to solve parts of its internal logic, and we will also discover elegant descriptors and that have been there in plain sight all along.

Functions and methods

The most resonating case of an object that is a descriptor is probably a function. Functions implement the __get__ method, so they can work as methods when defined inside a class.

In Python, methods are just regular functions, only they take an extra argument. By convention, the first argument of a method is named self, and it represents an instance of the class that the method is being defined in. Then, whatever the method does with self would be the same as any other function receiving the object and applying modifications to it.

In other words, when we define something like this:

class MyClass:
def method(self, ...):
self.x = 1
Defining a method in a class

It is actually the same as if we define this:

class MyClass: pass
def method(myclass_instance: MyClass, ...):
myclass_instance.x = 1
method(MyClass())
Equivalent to defining a method in a class

So, it is just another function, modifying the object, only it's defined inside the class, and it is said to be bound to the object.

When we call something in the form of this:

instance = MyClass()
instance.method(...)
Creating a class instance

Python is, in fact, doing something equivalent to this:

instance = MyClass()
MyClass.method(instance, ...)
Equivalent to creating a class instance

Note that this is just a syntax conversion that is ...