8

According to this answer:

setattr(instance, name, value) is syntactic sugar for instance.__setattr__(name, value)

But:

class C:
    def __init__(self):
        # OK.
        super().__setattr__("foo", 123)

        # AttributeError: 'super' object has no attribute 'foo'
        setattr(super(), "foo", 123)

c = C()

What gives? Shouldn't they both do the same thing?

Maxpm
  • 24,113
  • 33
  • 111
  • 170

1 Answers1

4

The answer you linked to glosses over some important details. Long story short, setattr bypasses super's magic, so it tries to set attributes on the super() proxy object itself instead of on self.


setattr(a, b, c) is not syntactic sugar for a.__setattr__(b, c). setattr and regular attribute assignment both look for a __setattr__ method through a direct search of an object's type's method resolution order (a carefully-ordered list of the class and all superclasses), bypassing both the instance dict and __getattribute__/__getattr__ hooks.

In contrast, a.__setattr__(b, c) just performs a regular attribute lookup for __setattr__, so it goes through __getattribute__ and __getattr__, and it checks the instance dict unless __getattribute__ says not to.


super implements a custom __getattribute__ method to do its attribute magic. super().__setattr__ uses this hook, finding C's superclass's __setattr__ and binding self, resulting in a method object for setting attributes on self.

setattr(super(), ...) bypasses __getattribute__. It performs the direct MRO search mentioned previously, searching through super's MRO, resulting in a method object for setting attributes on the super instance itself. The super instance doesn't have a __dict__ to store arbitrary attribute data in, so trying to set a foo attribute fails with a TypeError.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • So, the conceit of `super()` doesn't hold up under `setattr(super(), ...)` because `super()` doesn't actually have the `__mro__` it pretends to? Is this limitation officially documented anywhere? The [`super()` docs](https://docs.python.org/3/library/functions.html#super) have a note on it being "undefined for implicit lookups using statements or operators," but I don't think that this is an implicit lookup. – Maxpm May 17 '19 at 03:25
  • @Maxpm: This is just as implicit as the lookup involved in regular attribute access. Despite the very similar names, `setattr` is not an explicit `__setattr__` lookup. – user2357112 May 17 '19 at 04:05
  • As for the `super` docs, the `super` docs are really quite terrible. They don't even say enough to explain how ordinary usage of `super` works. That said, they do explain that `super` works by implementing `__getattribute__`, and `__getattribute__` isn't triggered for these kinds of operations. – user2357112 May 17 '19 at 04:16
  • @user2357112: Your answer doesn't explain the difference between `setattr(...)` and `instance.__setattr__(...)`. – Ethan Furman May 17 '19 at 05:34
  • @EthanFurman: I've added some more explanation. It's hard to find the right balance between not explaining enough and overexplaining too many distracting details. I could go on and on about the differences between regular attribute lookup, special method lookup, and `super.__getattribute__`, talk about `tp_setattro` and how the MRO search is usually optimized away, talk about the descriptor protocol, talk about how the descriptor protocol is also usually optimized away, etc. – user2357112 May 17 '19 at 09:09
  • @user2357112: I updated my answer on the other question. I'd appreciate your feedback. – Ethan Furman May 20 '19 at 22:04