Re:WTFPython: Exploring and understanding Python through sur...
by masklinn on Tuesday May 31, 2022 @02:54PM
(#31573370)
> The value of A.b can, incidentally be accessed via obj.b because obj.b does not exist and the lookup path for instance attributes includes the class. [...] the += operator, which calls the __iadd__ method on the left value or acts as add and then assign to the name on the left, if that name doesn't reference a value that supports __iadd__.
That is not correct. += will always perform the assignment, hence the classic oddball concatenation:
>>> v = ([],)
>>> v
([],)
>>> v[0] += [1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> v
([1],)
So `obj.b += [2]` desugars to something along the lines of: l = obj.b
l.extend(2)
obj.b = l
Hence if you call vars() on the instance or access its __dict__, you will see that the instance does have an intrinsic "b" attribute: >>> class A: b = [1]
...
>>> a = A()
>>> a.b += (2,)
>>> vars(a), a.__dict__
({'b': [1, 2]}, {'b': [1, 2]})
And if you re-set the attribute on the class, it won't affect the instance, which it would if the instance delegated to the class: >>> A.b = [5]
>>> a.b
[1, 2]
It seems to, but, actually, it doesn't.
> but wtf'ingly also alters the value of the class attribute "A.b" which completely alters the behavior of A.
Actually, all it does is call the __iadd__ method on the list that it is the value of A.b (which modifies that list in-place.)
The value of A.b can, incidentally be accessed via obj.b because obj.b does not exist and the lookup path for instance attributes includes the class.
There are two things going on here there are potential sources of confusion, because they behavior differently in different circumstances: object member access (which can get members from the object or, if they don't exist on the object, from the class) and the += operator, which calls the __iadd__ method on the left value or acts as add and then assign to the name on the left, if that name doesn't reference a value that supports __iadd__.