In Python, why do properties take priority over instance attributes?
Asked Answered
I

3

10

This article describes how Python looks up an attribute on an object when it executes o.a. The priority order is interesting - 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)? 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?

Individually answered 16/12, 2018 at 10:36 Comment(7)
AFAICT JavaScript uses the "naive" lookup order and works fine. The following code, equivalent to the Python example, prints 1: class C { constructor() { Object.defineProperty(this, 'a', {value: 1}); } get a() { return 2; } } var o = new C(); console.log(o.a);Individually
Do I understand correctly that "Why do data descriptors in Python exist?" is what your question boils down to? Are you aware that you could code your own non-data-descriptor "property" by writing a descriptor class?Pubes
@Pubes - AFAIK data descriptors (descriptors which have both __get__ and __set__) primarily exist to support properties. What I want to know is, why do data descriptors take priority over instance attributes?Individually
I assume because it's a way to make sure your getter logic runs.Pubes
I see it this way: data descriptors are the default case. When I have a __get__ and a __set__ method, I want these hooks to trigger regardless of the detail whether the attribute in question exists in the object's __dict__ or not. Using a non data descriptor is then for the special cases where you want the getter only to trigger if the attribute is not in the __dict__. Non data descriptors are great for __get__ting a value once and then caching it in the instance __dict__, for example.Pubes
This appears to be a dupe of python data and non-data descriptors (although there is no accepted answer there).Herschelherself
And another one: #22586827 ...seems to be a common question with no good answer on site yet.Herschelherself
C
5

Guido van Rossum himself (the ex-BDFL of Python) designed this feature when new-style classes were introduced with Python 2.2 back in 2001. The reasoning is discussed in PEP 252. The impact on attribute lookup is explicitly mentioned:

This scheme has one drawback: in what I assume to be the most common case, referencing an instance variable stored in the instance dict, it does two dictionary lookups, whereas the classic scheme did a quick test for attributes starting with two underscores plus a single dictionary lookup.

And:

A benchmark verifies that in fact this is as fast as classic instance variable lookup, so I'm no longer worried.

Cupped answered 18/12, 2018 at 15:11 Comment(0)
D
2

For old style classes (PEP 252):

The instance dict overrides the class dict, except for the special attributes (like __dict__ and __class__), which have priority over the instance dict.

Overriding the __dict__ or __class__ within the instance dict would break attribute lookup and cause the instance to behave in extremely weird ways.

In new-style classes, Guido chose the following implementation of attribute lookup to maintain consistency (PEP 252):

  1. Look in the type dict. If you find a data descriptor, use its get() method to produce the result. This takes care of special attributes like __dict__ and __class__.
  2. Look in the instance dict. If you find anything, that's it. (This takes care of the requirement that normally the instance dict overrides the class dict.)
  3. Look in the type dict again (in reality this uses the saved result from step 1, of course). If you find a descriptor, use its get() method; if you find something else, that's it; if it's not there, raise AttributeError.

In summary, the __dict__ and __class__ attributes are implemented as properties (data descriptors). To maintain a valid state, the instance dict cannot override __dict__ and __class__. Thus, properties (data descriptors) take precedence over instance attributes.

Destructor answered 21/12, 2018 at 19:45 Comment(0)
C
1

What I want to know is, why do data descriptors take priority over instance attributes?

If there isn't some method that takes priority over normal instance lookup, how do you expect to intercept instance attribute accesses? These methods can't be instance attributes themselves because that would defeat their purpose (at least without additional conventions about them, I think).

Besides the concise comment of @timgeb, I can't explain anything about descriptors better than the official Descriptor How To

Cruzeiro answered 21/12, 2018 at 0:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.