Reputation: 703
I've been reading about descriptors in the Descriptor HowTo Guide, and I'm confused by this sentence:
If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence.
How can the dictionary contain two items (a normal entry and a data descriptor) with the same name? Or are attributes that are descriptors not stored in __dict__
?
Upvotes: 5
Views: 447
Reputation: 1121594
The data descriptor lives in the class namespace, while the instance attribute lives in the instance namespace (so instance.__dict__
). These are two separate dictionaries, so there is no conflict here.
So for any given attribute lookup for the name foo
on an instance bar
, Python also looks at it's class (type(bar)
, named C
below), in the following order:
C.foo
is looked up. If it is a data descriptor, this is where the lookup ends. C.foo.__get__(bar, C)
is returned. Otherwise, Python will store this result for step 3 (no point in looking this up twice).
If C.foo
did not exist or is a regular attribute, then Python looks for bar.__dict__['foo']
. If it exists, it is returned. Note that this part is never reached if C.foo
is a data descriptor!
If bar.__dict__['foo']
does not exist, but C.foo
exists, then C.foo
is used. If C.foo
is a (non-data) descriptor, thet C.foo.__get__(bar, C)
is returned.
(Note that C.foo
is really C.__dict__['foo']
, but for simplicity sake I've ignored descriptor access on classes in the above).
Perhaps a concrete example helps; here are two descriptors, one is a data descriptor (there is a __set__
method), and the other is not a data descriptor:
>>> class DataDesc(object):
... def __get__(self, inst, type_):
... print('Accessed the data descriptor')
... return 'datadesc value'
... def __set__(self, inst, value):
... pass # just here to make this a data descriptor
...
>>> class OtherDesc(object):
... def __get__(self, inst, type_):
... print('Accessed the other, non-data descriptor')
... return 'otherdesc value'
...
>>> class C(object):
... def __init__(self):
... # set two instance attributes, direct access to not
... # trigger descriptors
... self.__dict__.update({
... 'datadesc': 'instance value for datadesc',
... 'otherdesc': 'instance value for otherdesc',
... })
... datadesc = DataDesc()
... otherdesc = OtherDesc()
...
>>> bar = C()
>>> bar.otherdesc # non-data descriptor, the instance wins
'instance value for otherdesc'
>>> bar.datadesc # data descriptor, the descriptor wins
Accessed the data descriptor
'datadesc value'
Upvotes: 6
Reputation: 23203
Consider following code snippet:
class X:
@property
def x(self):
return 2
x1 = X()
x1.__dict__['x'] = 1
print(x1.x)
This code prints 2, because data descriptor (defined on class) takes precedence over instance dictionary.
Upvotes: 1