Recently I read the official HOW-TO about Python descriptors, which actually derives from an essay written by Raymond Hettinger long time ago. But after reading it for several times, I am still confused about some parts of it. I will quote some paragraphs, followed by my confusions and questions.
If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
For objects, the machinery is in
object.__getattribute__()
which transformsb.x
intotype(b).__dict__['x'].__get__(b, type(b))
. The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to__getattr__()
if provided.For classes, the machinery is in
type.__getattribute__()
which transformsB.x
intoB.__dict__['x'].__get__(None, B)
.
b.x
) and a class (B.x
). However, here are my confusions:
b.x
into type(b).__dict__['x'].__get__(b, type(b))
and B.x
into B.__dict__['x'].__get__(None, B)
) still proceed? Is returning the attribute in this class's or instance's dict directly simpler?Non-data descriptors provide a simple mechanism for variations on the usual patterns of binding functions into methods.
Functions have a
__get__()
method so that they can be converted to a method when accessed as attributes. The non-data descriptor transforms aobj.f(*args)
call intof(obj, *args)
. Callingklass.f(*args)
becomes f(*args).
obj.f(*args)
call into f(obj, *args)
?klass.f(*args)
call into f(*args)
?__get__()
method play under the above circumstance?Is this the same with class? What is the precedence chain of a class if its dictionary has an entry with the same name as a data/non-data descriptor?
No, if an attribute is defined both in a superclass and a subclass, the superclass value is completely ignored.
if the attribute of a class or an instance is not a descriptor, will the transformation (i.e., transforms b.x into
type(b).__dict__['x'].__get__(b, type(b))
and B.x intoB.__dict__['x'].__get__(None, B))
still proceed?
No, it returns directly the object gotten from the class's __dict__
. Or equivalently, yes, if you pretend that all objects have by default a method __get__()
that ignores its arguments and returns self
.
If an instance's dictionary has an entry with the same name as a non-data descriptor, according to the precedence rule in the first quote, the dictionary entry takes precedence, at this time will the transformation still proceed? Or just return the value in its dict?
What is not clear in the paragraph that you quoted (maybe it's written down elsewhere) is that when b.x
decides to return b.__dict__['x']
, no __get__
is invoked in any case. The __get__
is invoked exactly when the syntax b.x
or B.x
decides to return an object that lives in a class dict.
Is non-data descriptors chosen because functions/methods can only be gotten, but cannot be set?
Yes: they are a generalization of the "old-style" class model in Python, in which you can say B.f = 42
even if f
is a function object living in the class B. This lets the function object be overriden with an unrelated object. The data descriptors on the other hand have a different logic in order to support property
.
What's the underlying mechanism for binding functions into methods? Since class dictionaries store methods as functions, if we call the same method using a class and its instance respectively, how can the underlying function tell whether its first argument should be self or not?
To understand this, you need to have "method objects" in mind. The syntax b.f(*args)
is equivalent to (b.f)(*args)
, which is two steps. The first step calls f.__get__(b)
; this returns a method object that stores both b and f. The second step calls the method object, which will in turn call the original f by adding b as extra argument. This is something which doesn't occur for B.f
, simply because B.f
, i.e. f.__get__(None, B)
, is just f (in Python 3). It's the way the special method __get__
is designed on function objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With