Reputation: 4706
I am trying to wrap my head around the concept of descriptors and I have had no success lately. This article of descriptor How to has really helped and at the same time it has also confused me. I am struggling with this example here
on why m.x
calls def __get__(self, obj, objtype):
class RevealAccess(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
>>> class MyClass(object):
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
I believe the reason i am struggling with this is because I am confused by the following statement in the article
The details of invocation depend on whether obj is an object or a class. 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
I am not sure what the author means here by object or class. Does object mean an instance of a class ? I understand that when we do instance.var
python internally does a instance.__dict__["var"]
if that is not found then it does class.__dict__["var"]
. I kind of lost it after that concept. Could anyone explain a little on how this example is working. How is the definition def __get__(self, obj, objtype):
being called with m.x
. I would be very grateful if anyone could clear this up.
Upvotes: 1
Views: 219
Reputation: 1121654
Yes, by object the author means an instance, not a class. The distinction comes from where the descriptor lives; the descriptor is defined on the class, so when accessing that descriptor directly on the class a different path is followed from when you access that descriptor on an instance of the class.
In your example, m
is an instance. That instance itself has no attribute m
('x' in m.__dict__
is False
). Python also looks at type(m)
to resolve that attribute, and type(m)
here is MyClass
. 'x' in MyClass.__dict__
is True
, and MyClass.__dict__['x'].__get__
exists, so Python now knows that that object is a descriptor.
So, because there is no m.__dict__['x']
, but there is a type(m).__dict__['x'].__get__
, that method is called with m
and type(m)
as arguments, leading to type(m).__dict__['x'].__get__(m, type(m))
.
If 'x' in MyClass.__dict__
is true but MyClass.__dict__['x'].__get__
did not exist (so that object is not a descriptor object), then MyClass.__dict__['x']
would have been returned directly.
The situation gets more interesting if you tried to add the x
attribute to the instance too. In that case it matters if MyClass.__dict__['x'].__set__
or MyClass.__dict__['x'].__delete__
exist, making the descriptor a data descriptor. Data descriptors always win in the case of a tie. So if MyClass.__dict__['x'].__get__
and at least one of MyClass.__dict__['x'].__set__
or MyClass.__dict__['x'].__delete__
exist, it doesn't matter anymore if m.__dict__['x']
also exists. Python won't even look for it.
However, if there are no __set__
or __delete__
methods on the descriptor object on the class, then m.__dict__['x']
wins and is returned. In that case the descriptor is a regular, non-data descriptor and it loses out to the instance attribute.
Last but not least, if 'x' in type(m).__dict__
is false (there is no object on the class), then m.__dict__['m']
is returned. The descriptor protocol is only applied to objects found on the class, never to attributes on the instance.
Upvotes: 2
Reputation: 251373
Does object mean an instance of a class ?
Yes.
How is the definition
def __get__(self, obj, objtype):
being called withm.x
.
Because of just what the documentation you quoted says:
For objects, the machinery is in
object.__getattribute__()
which transformsb.x
intotype(b).__dict__['x'].__get__(b, type(b))
.
object.__getattribute__
is mechanism that implements what happens when you do something like m.x
. So, explicitly:
m.x
type(m).__dict__['x'].__get__(m, type(m))
MyClass.__dict__['x'].__get__(m, MyClass)
RevealAccess(10, 'var "x"').__get__(m, MyClass)
so the last line calls the __get__
method of the RevealAccess
class.
Upvotes: 1