Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 432
Sir Visto Avatar asked Nov 07 '16 11:11

Sir Visto


People also ask

What is the difference between properties and descriptors in Python?

The most accessible technique is to use the property function to define get, set and delete methods associated with an attribute name. The property function builds descriptors for you. A slightly less accessible, but more extensible and reusable technique is to define descriptor classes yourself.

What is an attribute in Python?

Attributes of a class are function objects that define corresponding methods of its instances. They are used to implement access controls of the classes.

What is descriptor object in Python?

Descriptors are Python objects that implement a method of the descriptor protocol, which gives you the ability to create objects that have special behavior when they're accessed as attributes of other objects.

What is a descriptor object?

“Descriptors” are objects that describe some attribute of an object. They are found in the dictionary of type objects.


Video Answer


1 Answers

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'
like image 97
Martijn Pieters Avatar answered Oct 06 '22 14:10

Martijn Pieters