3

Python in a Nutshell describes the lookup procedure when getting an attribute from a class, e.g. cls.name and the lookup procedure when getting an attribute from an instance, e.g. obj.name

But I am not sure when it comes to setting an attribute:

Setting an attribute

Note that the attribute lookup steps happen as just described only when you refer to an attribute, not when you bind an attribute. When you bind (on either a class or an instance) an attribute whose name is not special (unless a __setattr__ method, or the __set__ method of an overriding descriptor, intercepts the binding of an instance attribute), you affect only the __dict__ entry for the attribute (in the class or instance, respectively). In other words, for attribute binding, there is no lookup procedure involved, except for the check for overriding descriptors.

My questions are:

  • What is the "lookup" procedure when setting an attribute from a class, e.g. cls.name=value?

  • What is the "lookup" procedure when setting an attribute from an object, e.g. obj.name=value?

The procedures seem to involve

  • the __setattr__ method

    __setattr__(self, name, value)

    At every request to bind attribute x.y (typically, an assignment statement x.y=value, but also, for example, setattr(x, 'y', value)), Python calls x.__setattr__('y', value). Python always calls __setattr__ for any attribute binding on x—a major diff erence from __getattr__ (__setattr__ is closer to __getattribute__ in this sense). To avoid recursion, when x.__setattr__ binds x’s attributes, it must modify x.__dict__ directly (e.g., via x.__dict__[name]=value); even better, __setattr__ can delegate the setting to the superclass (by calling super(C, x).__setattr__('y', value) or, in v3, just super().__setattr__('y', value)). Python ignores the return value of __setattr__. If __setattr__ is absent (i.e., inherited from object), and C.y is not an overriding descriptor, Python usually translates x.y=z into x.__dict__['y']=z.

  • the __set__ method of an overriding descriptor (So I asked Why does `object.__get__` have an argument `owner` for the owner class, while `object.__set__` doesn't?)

  • the __dict__ entry for the attribute (in the class or instance, respectively)

Given that the book distinguishes the lookup procedures for getting an attribute from a class and from an instance, it is natural to think that the "lookup" procedures are different when setting an attribute from a class and when setting an attribute from an instance.

But since in Python3, every class object is actually an instance of its metaclass (e.g. type class), are the "lookup" procedure for setting an attribute from a class and the "lookup" procedure for setting an attribute from an instance really different?

Thanks.

Tim
  • 1
  • 141
  • 372
  • 590
  • 1
    Very simple: it assigns to that class's attribute. It doesn't go to the base classes. – Barmar Jul 06 '17 at 00:36
  • The quote from the book doesn't seem so simple. – Tim Jul 06 '17 at 00:42
  • @Tim Your question is answered in the last sentence of your second quote: _"In other words, for attribute binding, there is no lookup procedure involved, except for the check for overriding descriptors."_. – Christian Dean Jul 06 '17 at 00:43
  • I am not sure I understand that sentence, and I think that sentence can't replace the whote paragraph, because the paragraph implies there is still some "lookup" procedure, and that sentence also misses `__setattr__` method in the paragraph. @ChristianDean – Tim Jul 06 '17 at 00:45
  • @Tim What's complicated about "you affect only the `__dict__` entry for the attribute"? – Barmar Jul 06 '17 at 00:47
  • The parts I made in bold. @Barmar – Tim Jul 06 '17 at 00:48
  • Attribute retrieval goes through `__getattribute__` and `__getattr__` methods similar to `__setattr__` for setting. The resource you're reading doesn't seem to mention those, at least in the section you quoted. – user2357112 Jul 06 '17 at 00:49
  • @Tim Those are probably explained elsewhere. There's no generic description for them, because the overriding methods can do whatever they want. – Barmar Jul 06 '17 at 00:50
  • @user2357112: For getting part, I updated https://stackoverflow.com/q/44913505/156458. For setting part, I will update my post here. – Tim Jul 06 '17 at 00:52
  • What edition are you reading? – user2357112 Jul 06 '17 at 00:53
  • 3ed in 2017 @user2357112 – Tim Jul 06 '17 at 00:58

1 Answers1

2

For x.y = z, Python looks up x.__setattr__ in a way that bypasses __getattribute__, __getattr__, and x's own __dict__, then calls x.__setattr__('y', z).

A custom __setattr__ can do whatever the heck it wants, but here's the default:

  1. Search type(x).__mro__ for a __dict__ entry matching the attribute name.

  2. If this search finds a descriptor descr with a __set__ method (looking up __set__ the same way we looked up __setattr__), call descr.__set__(x, z).

    2.a. In the highly unusual case that the search finds a descriptor with __delete__ but no __set__, raise an AttributeError.

  3. Otherwise, set x.__dict__['y'] = z, or raise an AttributeError if x has no __dict__. (This uses the "real" __dict__, bypassing things like mappingproxy.)

This applies to both types and other objects, with the caveats that type.__setattr__ will refuse to set attributes of a class written in C, and type.__setattr__ does some extra internal maintenance you'll never need to worry about unless you do something crazy and unsupported to bypass it.

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Thanks. (1) Does the first step mean that find a class in `type(x).__mro__` which has a class attribute named `y`? If yes and we find a class which can be an ancestry class of `type(x)`, why set the instance attribute `x.y` in `descr.__set__(x, z)` in step 2 and in `x.__dict__['y'] = z` in step 3, instead of setting the found class' class attribute `y`? (2) Does step 3 happen when the search in step 1 fails to find any class, or when the search in step 1 finds some class but the found attribute isn't a descriptor in step 2? – Tim Jul 06 '17 at 22:24
  • (3) if you can add some examples for each case, that will help to understand. (4) Is you reply based on some documents or the actual code implementation? – Tim Jul 06 '17 at 22:27
  • @Tim: 1) That's just how the language was designed. By default, setting an object's attribute is always an operation on that object, even if there's an existing class attribute. 2) Either. – user2357112 Jul 06 '17 at 22:29
  • Thanks. "By default, setting an object's attribute is always an operation on that object, even if there's an existing class attribute. " However, step 1 "Search `type(x).__mro__` for a `__dict__` entry matching the attribute name" seems to search for a class in `type(x).__mro__` which has `y` as its class attribute. Maybe I misunderstand, so what does step 1 mean? – Tim Jul 06 '17 at 22:36
  • @Tim: It's looking for a descriptor to delegate the operation to, not an attribute to assign to. – user2357112 Jul 06 '17 at 22:41
  • Thanks. how did you learn the procedure of setting attributes? – Tim Jul 07 '17 at 01:17
  • @Tim: Assorted docs, and I think a combination of experiment and checking the implementation to verify that descriptor handling was `__setattr__`'s responsibility instead of handled outside it somehow. – user2357112 Jul 07 '17 at 01:21
  • Thanks. In the book (see the quote in my post), it describes the setting procedure "When you bind (on either a class or an instance) **an attribute whose name is not special** ". (1) Does a special name mean any name which begins and ends with `__`? (2) When an attribute has a special name, what is the procedure of **setting** the attribute? (3) When an attribute has a special name, what is the procedure of **getting** the attribute? – Tim Jul 07 '17 at 01:23
  • @Tim: Which ones are "special" is entirely decided by the type and its implementation. My explanation applies to those attributes too; either they'll be handled specifically inside a custom `__setattr__`, or a descriptor will handle them. For example, assigning to an object's `__dict__` will either be special-cased by `__setattr__` or handled by a descriptor instead of going to a dict entry. – user2357112 Jul 07 '17 at 01:44
  • What is the rationale for the step 2.a (raising `AttributeError` if the class attribute is a *data* descriptor without the corresponding method) which exists only for `__set__` and `__delete__` but not for `__get__`? – Géry Ogam Mar 22 '21 at 11:24
  • (I have opened a new post [here](https://stackoverflow.com/q/66754075/2326961) for this question, if you are interested.) – Géry Ogam Mar 22 '21 at 21:41