Is it ironic that the documentation for descriptors is not very descriptive
Descriptors are one of my favorite Python features — but it took me too long to discover them. The documentation and tutorials that I found were too complex for me. So I would like to offer a different approach, a code first approach
- A Problem that Descriptors Can Solve
- CODE!! Solution to the Problem
- Reflection on the Code, and explanation on how we used Descriptors
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
Read that and stash it in your brain for a few minutes. By the end of this article you’ll grok this
A Problem that Descriptors Can Solve
Imagine that you need to consume a 3rd party API that returns json documents. Often solutions to this problem look like this …
1 2 3
I dislike this solution, Its concise, but it breaks separation-of-concerns. The code consuming the API should not be concerned about the exact path and location of the data element in the json document.
Proposed Solution with Descriptors:
Our goal is to write code like this:
1 2 3
CODE!! Solution to the Problem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Now look how elegant our code can look
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
And now are code is warm and fuzzy:
1 2 3
Reflection on the Code
The real magic of descriptors happens with the signatures of __get__(), __set__(), and __delete__():
object.__get__(self, instance, owner)
object.__set__(self, instance, value)
Each of these signatures contains a reference to
instance, which is the instance of the owner’s class. So in our example:
instance will be an instance of the User class
owner will be the User class
self is the instance of the Descriptor, which in our case holds the
Let’s take a look at our example where we made a descriptor Extractor.
user = UserAPI.get_by_id(111)
Here we get an instance of a User object, which has the json_blob stored on it from the GET request
Now we call
name on that object, which we defined:
name = Extractor('result','username'). At this point when we call
name it is going to use the Extractor descriptor to extract the value from the json_blob.
The concern of extracting data from a json blob is nicely contained in our Descriptor I think this is one of many great ways to use descriptors to DRY up your code.
Hope this is helpful!