Why do data descriptors (e.g. properties) take priority over instance attributes?

Ian Kelly ian.g.kelly at gmail.com
Tue Dec 18 09:54:06 EST 2018

On Mon, Dec 17, 2018, 12:09 PM Paul Baker <paulbaker8 at gmail.com wrote:

> When Python looks up an attribute on an object (i.e. when it executes
> `o.a`), it uses an interesting priority order [1]. It looks for:
>  1. A class attribute that is a data-descriptor (most commonly a property)
>  2. An instance attribute
>  3. Any other class attribute
> We can confirm this using the code below, which creates an object `o`
> with an instance attribute `a`, whose class contains a property of the
> same name:
>     class C:
>         def __init__(self):
>             self.__dict__['a'] = 1
>         @property
>         def a(self):
>             return 2
>     o = C()
>     print(o.a)  # Prints 2
> Why does Python use this priority order rather than the "naive" order
> (instance attributes take priority over all class attributes, as used
> by JavaScript)? Python's priority order has a significant drawback: it
> makes attribute lookups slower, because instead of just returning an
> attribute of `o` if it exists (a common case), Python must first
> search `o`'s class *and all its superclasses* for a data-descriptor.
> What is the benefit of Python's priority order? It's presumably not
> just for the above situation, because having an instance variable and
> a property of the same name is very much a corner case (note the need
> to use `self.__dict__['a'] = 1` to create the instance attribute,
> because the usual `self.a = 1` would invoke the property).
> Is there a different situation in which the "naive" lookup order would
> cause a problem?

It would create a disparity between lookup and assignment. When you assign
to an attribute that has a data descriptor, it always invokes the
descriptor. If it preferred to assign to the instance dict instead, the
descriptor would never be invited.

Now, suppose you have an object with a data descriptor, and an instance
attribute of 42. We read the attribute and get 42 back, which is not what
we want, so we assign 111 to it, then we read it back again, and the value
is still 42! What gives? We're not able to overwrite or delete the value
that were reading, and it's because an unexpected value crept into the
instance dict somehow. The storage of a data descriptor value is expected
to be defined by the descriptor, not by using the same key in the instance
dict (although this scheme also means that the data descriptor could use
that dict entry for its own storage if it wants).

The reason you can do this for a non-data descriptor is because with no set
or delete, they don't define their own storage, and you can thus set the
instance attribute if you want to override the descriptor.


More information about the Python-list mailing list