5

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__?

2 Answers 2

6

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' 
Sign up to request clarification or add additional context in comments.

3 Comments

OK, so data descriptors are in the class dictionary. The "Descriptor HowTo Guide" seems to suggest the instance dictionary is searched before the class dictionary though: "For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses. "
@SirVisto the howto is simplifying there. If you took data descriptors out of the picture that order is perfectly correct. To introduce data descriptors at that point would needlessly complicate the story being told.
Actually, the article does clarify this later on: "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."
1

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.

3 Comments

What is the rationale for this? I would intuitively expect an instance attribute to shadow a class attribute with the same name...
@SirVisto: this enables the data descriptor to fully control access (so including setting and deleting). If the instance dict got precedence, then del inst.foo would sometimes reach the instance, sometimes the data descriptor. For setting, what should happen when you do inst.foo = 'value', should it matter if there is an instance attribute already? Who is then responsible for handling the setting? By consistently always making the data descriptor responsible you resolve all those questions clearly and consistently.
@SirVisto: another motivation is the ability to make sure that some class attributes can't be accidentally overridden in the instance. The __class__ attribute for example, should never be set on the instance, because then you can no longer find the corresponding class, so __class__ is a data descriptor on the class.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.