Python's Descriptors - Part II - The true magic
Part I: pythons-descriptors-part-i-let-the-hunt-begin/
In part I, we started the travel to find the way to craft Python’s descriptors. We known why we need descriptors, and some trouble to know why descriptors are not easy to implement. However, it’s just a way to implement our code, right? So keep going, in this part we will find another way to do a right descriptor.
Descriptor with dictionary
In part I, we are going to find a way to store kind
instance separated for each Dog
. I was thinking about dictionary to store kind
for each Dog
, so let’s try to use some dictionary. But first, do you notice the instance
param in descriptor’s protocol (__set__()
, __get__()
, __delete__()
). Take a look:
1 | class StringField(object): |
1 |
|
Actually, instance
is the object have descriptors as its attributes. So I go to this implement:
1 | from weakref import WeakKeyDictionary |
1 | DOG a: Husky |
Yes, it worked. But it’s not done yet. Let’s think about dictionary’s key. Dictionary’s keys must be hashable, so if instance
is not hashable objects, such as list
, our code will be ruined.
1 | class List(list): |
1 | Traceback (most recent call last): |
It’s not a really big problem if we can use a label for each instance to avoid it. Unfortunately, we can’t set weakref key is str
or int
. At this time, I find out I forgot a python build-in __dict__
Finally
No more dictionary
So, let’s change one or two lines of code, we will have descriptors, and believe me, it will work this time.
1 | class StringField(object): |
1 | Husky None |
Yes. It’s worked now. However, we will repeat label as attributes name every time. Not good huh? Actually, this is an acceptable way to do descriptors and it’s fairly common. There are some books using this implementation. However, I don’t like it because as I mentioned in Part I, I’m going to find a way to do data objects as Mongoengine did, and I will continue my travel. We have nearly done. We need to find a way to set label automatically by attributes name.
Auto set label with metaclasses
Yeah, the true magic behind the most beautiful way to craft descriptors is metaclass. A metaclass is the class of a class. Like a class defines how an instance of the class behaves, a metaclass defines how a class behaves. A class is an instance of a metaclass. This blog will explain it more detailed.
So, let’s apply metaclass in our code and finish it:
1 | class DescriptorOwner(type): |
1 | Husky None |
OK, now our descriptors are completed and work like a charm, like some libraries such as Mongoengine do. We can do many things with descriptors not just check attribute’s type, such as setting value is nullable or not, setting default value or calling another function to do some triggers …
Conclusion
So, this is a full road since I started the hunt to find descriptor’s recipe, every single step I walked is detailed. I hope you now have an understanding of what descriptors are, and how to use it. Feel free to leave your comments and discuss about descriptors here.