In the previous section we used a descriptor with both
__get__() and __set__()
methods. Such descriptors, by the way, are called data
descriptors. Descriptors with only the
__get__() method are somewhat weaker than their
cousins, and called non-data descriptors.
Repeating our experiment, but this time with non-data descriptors, we get:
Calls | |
Puts
| |
Surprise!
This now returns | |
Deletes the
attribute | |
These function identical to a data descriptor. |
Interestingly, not having a __set__() affects not
just attribute setting, but also retrieval. What is Python thinking?
If on setting, the descriptor gets fired and puts the data somewhere,
then it follows that the descriptor only knows how to get it back. Why
even bother with the instance's __dict__?
Data descriptors are useful for providing full control over an attribute. This is what one usually wants for attributes used to store some piece of data. For example an attribute that gets transformed and saved somewhere on setting, would usually be reverse-transformed and returned when read. When you have a data descriptor, it controls all access (both read and write) to the attribute on an instance. Of course, you could still directly go to the class and replace the descriptor, but you can't do that from an instance of the class.
Non-data descriptors, in contrast, only provide a value when an instance itself does not have a value. So setting the attribute on an instance hides the descriptor. This is particularly useful in the case of functions (which are non-data descriptors) as it allows one to hide a function defined in the class by attaching one to an instance.
Calls the bound
method returned by | |
Calls
|
cobj.d = "setting a value"
x = cobj.d
del cobj.d
x = C.d
C.d = "setting a value on class"