1

From Python in a Nutshell

Getting an attribute from an instance

When you use the syntax x.name to refer to an attribute of instance x of class C , the lookup proceeds in three steps:

  1. When name is found in C (or in one of C’s ancestor classes) as the name of an overriding descriptor v (i.e., type(v) supplies methods __get__ and __set__ )

    The value of x.name is the result of type(v).__get__(v, x, C)

  2. Otherwise, when name is a key in x.__dict__

    x.name fetches and returns the value at x.__dict__['name']

  3. Otherwise, x.name delegates the lookup to x’s class (according to the same two-step lookup used for C.name, as just detailed)

    When a descriptor v is found, the overall result of the attribute lookup is, again, type(v).__get__(v, x, C)

    • When a nondescriptor value v is found, the overall result of the attribute lookup is just v

When these lookup steps do not find an attribute, Python raises an AttributeError exception. However, for lookups of x.name , when C defines or inherits the special method __getattr__ , Python calls C.__getattr__(x,'name') rather than raising the exception. It’s then up to __getattr__ to either return a suitable value or raise the appropriate exception, normally AttributeError .

  1. Are step 1 and the first part of step 3 the same? If yes, why does the same step appear twice?

  2. Do they both happen "when name is found in C (or in one of C’s ancestor classes) as the name of an overriding descriptor v"?


__getattribute__(self, name) 

At every request to access attribute x.y, Python calls x.__getattribute__('y'), which must get and return the attribute value or else raise AttributeError. The normal semantics of attribute access (using x.__dict__, C.__slots__, C’s class attributes, x.__getattr__) are all due to object.__getattribute__. When class C overrides __getattribute__, it must implement all of the attribute access semantics it wants to offer. Most often, the most convenient way to implement attribute access semantics is by delegating (e.g., calling object.__getattribute__(self, ...) as part of the operation of your override of __getattribute__).

Tim
  • 1
  • 141
  • 372
  • 590

1 Answers1

2

Are step 1 and the first part of step 3 the same? If yes, why the same step appear twice?

Step 1 requires both __get__ and __set__ (although actually, either __set__ or __delete__ along with __get__ would trigger it). Step 3 happens unconditionally if the attribute isn't found through steps 1 or 2.

Do they both happen "when name is found in C (or in one of C’s ancestor classes) as the name of an overriding descriptor v"?

No. An "overriding descriptor" triggers step 1; another kind of descriptor or a non-descriptor will only be considered in step 3. (The official Python docs don't use the term "overriding descriptor"; they refer to a descriptor with __set__ or __delete__ as a "data descriptor", and if a data descriptor has __get__, the __get__ will take priority over an object found in an instance dict.)

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Thanks. What do "overriding" and "nonoverriding" mean when they qualify "descriptor"? – Tim Jul 04 '17 at 22:58
  • @Tim: They're just referring to whether it overrides a value found in the instance dict. – user2357112 Jul 04 '17 at 23:13
  • Thanks. (1) Is "either `__set__` or **`__delete__`** along with `__get__` would trigger it" mentioned in some documents? (2) In " if a data descriptor has `__get__`, the `__get__` will take priority over an object found in an instance dict", do you mean `__set__` instead of `__get__`? – Tim Jul 05 '17 at 01:25
  • @Tim: https://docs.python.org/3/reference/datamodel.html#invoking-descriptors, and no, I mean `__get__`. – user2357112 Jul 05 '17 at 01:26
  • Thanks. (2) When you wrote " if a data descriptor has `__get__`", I misread it as " if a descriptor has `__get__`". Doesn't a descriptor's class always have `__get__`? Why do you use "if"? – Tim Jul 05 '17 at 01:29
  • @Tim: It's unusual but permitted for a descriptor to have no `__get__`. – user2357112 Jul 05 '17 at 01:31
  • Can you provide a document to back up your comment? In Python in a Nutshell, "A descriptor is any object whose class supplies a special method named `__get__`". – Tim Jul 05 '17 at 01:33
  • @Tim: That line is wrong. Maybe they were glossing over things, or maybe they just weren't aware of all the little details, or maybe the error was introduced in editing or something. – user2357112 Jul 05 '17 at 01:40
  • 1
    From the same docs link: "In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: `__get__()`, `__set__()`, and `__delete__()`. If **any** of those methods are defined for an object, it is said to be a descriptor." – user2357112 Jul 05 '17 at 01:40