Sir Visto
Sir Visto

Reputation: 703

Python attributes and descriptors

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

Answers (2)

Martijn Pieters
Martijn Pieters

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:

  1. 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).

  2. 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!

  3. 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

Łukasz Rogalski
Łukasz Rogalski

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

Related Questions