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

10. Descriptors Are Classes Too

Jacob Zimmerman1 
(1)
New York, USA
 

It’s time for some more advanced stuff with descriptors. Actually, it’s not really advanced, since it’s stuff that pertains to all classes. There won’t be a very in-depth look at much in this chapter; it’s just a reminder that features normally available to classes are available to descriptors as well.

Inheritance

Descriptors can inherit and be inherited from other classes (which will generally be other descriptors or descriptor utilities/helpers). Using inheritance, descriptors can be built using pre-built mixins and template classes that already implement the base functionality wanted for storing the attribute. In fact, a suite of these are discussed in the next chapter and fully provided in the library. Just as an example, a base class can be created that takes care of the minor details of using on-descriptor storage that the derived specialization can delegate to. Again, there’s more about this idea in the next chapter, with full code examples in the library.

More Methods

A descriptor can have more methods than just that of the descriptor protocol, __set_name__(), and __init__(). This was shown with secret-set descriptors that have a back door method, like set().

Externally-used methods like that should be limited, since access to these methods should be limited too, but using internally-used “private” methods that are used only within the class are definitely fair game. Also, implementing __str__() and __repr__() is a good idea too. It’s rarely useful or necessary to implement __eq__() or __hash__(), as descriptors themselves aren’t likely compared or stored in a hashed collection as a key.

Optional/Default Parameters

Just like in the forced-set descriptors, optional/default parameters can be added to the protocol methods. Since users providing alternative arguments still requires them to get the descriptor object and call the protocol methods directly, this should be limited, just like additional externally-used methods.

Additionally, it should be limited for the sake of composition and inheritance. If the class providing the optional parameter gets wrapped or subclassed, the new class either has to know about the optional parameter or provide a **kwargs parameter and pass it down the line, as will be seen in much of the provided code in the library.

Descriptors on Descriptors

Since descriptors are classes, descriptors can have descriptors on them too! There have been several times that I almost did so, but the setter was more complicated than what descriptors provide, so I had to settle. I’ve also considered using descriptors to make the attributes read-only, but I’ve never fully settled on it.

Passing an Instance Around

No one ever said that a descriptor had to create a new instance for each and every class it was put on. An instance of a descriptor can be created outside of a class definition, then assigned to a class attribute of multiple classes.

This can save a little bit of space when storing on the descriptor, since it will only have the overhead of a single dictionary instead of one per class. In fact, if you’re storing the values on the descriptor, it’s much less of a problem than saving on the instance. The issue with descriptors storing the values on the instance is that you need the name to store it on, and if that name is supposed to the same as or derived from the name the descriptor has on the class, you have to deal with the possibility that the descriptor has multiple names. Interestingly, __set_name__() is called each time you assign the descriptor to a class in the class definition. If you don’t need the name (you should, for error messages), you can still get away with a single descriptor used on multiple classes. The best use case is when the descriptor is really specific and used with the same name on every class. This eliminates all of the problems.

But if you want to use a single instance of a descriptor across multiple classes that can potentially use a different name for it, you’ll need to create a specialized storage for those names that is keyed by classes, but can also take inheritance into account. I would actually enjoy the challenge and have considered creating one to put into descriptor-tools, but I don’t want to encourage the idea too much.

Whatever you do, do not reuse the same descriptor for multiple attributes on the same class. It simply won’t work. All the attributes will have the same value.

Descriptors Just Abstract Method Calls

Basically, a descriptor is just a simpler way to do certain method calls. Those method calls don’t have to work in a property-ish way, getting and/or setting a certain value.

The __get__() descriptor method can essentially replace any method on a class that takes no parameters and returns an object. What’s more, it doesn’t even need to return anything, since not returning anything means it returns None. The __set__() descriptor method can be a replacement for any method that has a single parameter and doesn’t return anything. The __delete__() method replaces methods with no parameters and doesn’t return anything.

While a descriptor can be used in these ways, doing so is very likely to be unintuitive to users of the descriptor, largely due to the fact that the syntax seems strange for many of those cases, especially in the case of __delete__().

Summary

Anything that can be done with any other class can be done with a descriptor, including things not brought up here. Although much of it can be done without any real downsides, there is rarely a need for many of the features, but it doesn’t hurt to keep all of this in mind when writing your descriptors.

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

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