[Python-Dev] Descriptor write-up [Draft: Please
comment]
Phillip J. Eby
pje@telecommunity.com
Sun, 01 Jun 2003 06:32:36 -0400
At 04:32 AM 6/1/03 -0400, Raymond Hettinger wrote:
>In the spirit of Michele Simionato's write-up on method resolution order,
>I've written up almost everything I know about
>descriptors:
>http://tinyurl.com/d63d
>
>Key features:
>* Defines a descriptor
>* Shows the protocol and how it is called
>* Shows a handmade descriptor
>* Then covers functions, properties, static and class methods
>* Gives examples of each
>* Suggests when each would be useful
>* Gives a pure python definition of each
>* Provides exact references into C source to save efforts in hunting down
>details
>
>I would like to hammer this into something really useful. So, any and all
>suggestions are welcome.
It would be a good idea to add some information about "data" and "non-data"
descriptors, and the differences of how their attribute lookup is
processed. I recently posted here about the "attribute lookup process" or
something to that effect, which covered this. Understanding data vs.
non-data descriptors is important if you want to do pretty much anything
with descriptors beyond what property() does.
This section:
"Alternatively, a descriptor is invoked automatically upon attribute
access. For example, obj.a looks up a in the dictionary of obj. If obj
defines the method __get__, then it is automatically invoked if obj is a
new style class or object."
isn't accurate. I think you meant to say that if 'a' defines __get__, then
it's invoked. But this isn't accurate either. If obj.__dict__ has an 'a'
entry, and the descriptor is a non-data descriptor, __get__ will be ignored
and the contents of obj.__dict__ will be returned. (One interesting effect
of this is that you can create a non-data descriptor with a __get__ that
performs a computation and stashes the result in the instance dictionary -
from then on the computed value is returned.)
The Python code you have for the algorithm is also incorrect. Here's a
more accurate depiction of the process. It's not a straight translation of
PyGeneric_GetAttr, but an attempt to do as you have done, i.e. write a pure
Python __getattribute__ with equivalent semantics.
def __getattribute__(self, key):
dict = object.__getattribute__(self, '__dict__')
try:
# Does the class have a descriptor for this attribute?
descr = getattr(type(self),key)
except AttributeError:
# No, just use what's in the dictionary
try:
return dict[key]
except KeyError:
raise AttributeError
if hasattr(descr,'__set__') and hasattr(descr,'__get__'):
# It's a data descriptor, so use the get method
return descr.__get__(self,type(self))
# non-data descriptor, use __dict__ first, *then* __get__
try:
return dict[key]
except KeyError:
if hasattr(descr, '__get__'):
return descr.__get__(self, type(self))
# not a descriptor, return it as-is
return descr
As you can see, it's a bit more complex than your writeup implies. :)