© Jacob Zimmerman 2018
Jacob ZimmermanPython Descriptorshttps://doi.org/10.1007/978-1-4842-3727-4_2

2. The Descriptor Protocol

Jacob Zimmerman1 
(1)
New York, USA
 

In order to get a better idea of what descriptors are good for, let’s finish showing the full descriptor protocol. It’s time to see the full signatures of the protocol’s methods and what the parameters are.

The __get__(self, instance, owner) Method

This method is clearly the method for retrieving whatever data or object the descriptor is meant to maintain. Obviously, self is a parameter, since it’s a method. Also, it receives instance and/or owner. We’ll start with owner.

owner is the class that the descriptor is accessed from, or else the class of the instance it’s being accessed from. When you make the call A.x (A being a class), and x is a descriptor object with __get__(), it’s called with an owner with the instance set to None. So the lookup gets effectively transformed into A.__dict__['x'].__get__(None, A). This lets the descriptor know that __get__() is being called from a class, not an instance. owner is also often written to have a default value of None, but that’s largely an optimization that only built-in descriptors take advantage of.

Now, onto the last parameters. instance is the instance that the descriptor is being accessed from, if it is being accessed from an instance. As previously stated, if None is passed into instance, the descriptor knows that it’s being called from the class level. But, if instance is not None, then it tells the descriptor which instance it’s being called from. So an a.x call will be effectively translated to type(a).__dict__['x'].__get__(a, type(a)). Notice that it still receives the instance’s class. Notice also that the call still starts with type(a), not just a, because descriptors are stored on classes. In order to be able to apply per-instance as well as per-class functionality, descriptors are given instance and owner (the class of the instance). How this translation and application happens will be discussed later.

Remember—and this applies to __set__() and __delete__() as well—self is an instance of the descriptor itself. It is not the instance that the descriptor is being called from; the instance parameter is the instance the descriptor is being called from. This may sound confusing at first, but don’t worry if you don’t understand for now—everything will be explained further.

The __get__() method is the only one that bothers to get the class separately. That’s because it’s the only method on non-data descriptors, which are generally made at a class level. The built-in decorator classmethod is implemented using descriptors and the __get__() method. In that case, it will use the owner parameter alone.

The __set__(self, instance, value) Method

As mentioned, __set__() does not have an owner parameter that accepts a class. __set__() does not need it, since data descriptors are generally designed for storing per-instance data. Even if the data is being stored on a per-class level, it should be stored internally without needing to reference the class.

self should be self-explanatory now; the next parameter is instance. This is the same as it is in the __get__() method. In this case, though, your initial call is a.x = someValue, which is then translated into type(a).__dict__['x'].__set__(a, someValue).

The last parameter is value, which is the value the attribute is being assigned.

One thing to note: when setting an attribute that is currently a descriptor from the class level, it will replace the descriptor with whatever is being set. For example, A.x = someValue does not get translated to anything; someValue replaces the descriptor object stored in x . To act on the class, see the following note.

The __delete__(self, instance) Method

After having learned about the __get__() and __set__() methods, __delete__() should be easy to figure out. self and instance are the same as in the other methods, but this method is invoked when del a.x is called and is translated to type(a).__dict__['x'].__delete__(a).

Do not accidentally name it __del__(), as that won’t work as intended. __del__() would be the destructor of the descriptor instance, not of the attribute stored within.

It must be noted that, again, that __delete__() does not work from the class level, just like __set__(). Using del from the class level will remove the descriptor from the class’ dictionary rather than calling the descriptor’s __delete__() method.

Note

If you want a descriptor’s __set__() or __delete__() methods to work from the class level, that means that the descriptor must be created on the class’ metaclass. When doing so, everything that refers to owner is referring to the metaclass, while a reference to instance refers to the class. After all, classes are just instances of metaclasses. The section on metadescriptors will explain that in greater detail.

Summary

That’s the sum total of the descriptor protocol. Having a basic idea of how it works, you’ll now get a high-level view of the types of things that can be done with descriptors.

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

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