News for hackers. Stuff that matters.
Re:WTFPython: Exploring and understanding Python through sur... by iknownothow on Tuesday May 31, 2022 @04:40AM (#31567292)
Maybe we are looking at it differently. To me, the behavior of "obj.a += (2, )" is the obvious and expected one i.e. I expect a tuple "(1, 2)" to be created and stored in "obj.a" and the class attribute "A.a" is untouched and remains "(1,)". The behavior of the "obj.b += [2,]" for the list is the real gotcha here where it correctly alters the value of "obj.b" but wtf'ingly also alters the value of the class attribute "A.b" which completely alters the behavior of A.

I'm curious if you see it the same way?

Re:WTFPython: Exploring and understanding Python through sur... by dragonwriter on Tuesday May 31, 2022 @06:45AM (#31568008)
> The behavior of the "obj.b += [2,]" for the list is the real gotcha here where it correctly alters the value of "obj.b"

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__.

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]
Re:WTFPython: Exploring and understanding Python through sur... by praptak on Tuesday May 31, 2022 @05:02AM (#31567397)
My intuitions come from C and C++, so "+=" should always modify a single object (in C lingo it would be "lvalue") in place. Storing the result somewhere else than the left side of "+=" is counterintuitive to me.

Side note: the object can be immutable, but then the (variable or attribute) left side should be replaced with a new immutable object.

Re:WTFPython: Exploring and understanding Python through sur... by cecilpl2 on Tuesday May 31, 2022 @02:32PM (#31573085)
> My intuitions come from C and C++, so "+=" should always modify a single object (in C lingo it would be "lvalue") in place. Storing the result somewhere else than the left side of "+=" is counterintuitive to me.

I thought of this as though the list A.b was marked static - then any modification of b through any instance of A modifies the shared static member

  >>> class A:
  ...   b = [1,]
  ...
  >>> obj = A()
  >>> obj.b.append(2)
  >>> A.b
  [1, 2]
  >>> foo = A()
  >>> foo.b
  [1, 2]
  >>>
Re:WTFPython: Exploring and understanding Python through sur... by iknownothow on Tuesday May 31, 2022 @05:20AM (#31567486)
Gotcha, I see where we differ. I've exclusively programmed in Python and since int, str and tuple are immutable in Python, I've always assumed "+=" operators create copies and don't update the object.
Re:WTFPython: Exploring and understanding Python through sur... by kazinator on Tuesday May 31, 2022 @05:23AM (#31567499)
> int is immutable

But what does that have to do with assignment?

  >>> a = 3
  >>> a += 4
  >>> print(a)
  7
3 and 7 are immutable, yet a takes on those objects.

Obviously, this is inspired by the C += operator, and behaves accordingly, when it's not being weird.

Re:WTFPython: Exploring and understanding Python through sur... by iknownothow on Tuesday May 31, 2022 @06:17AM (#31567817)
If you go back to the original comment from the user praptak, you will notice in his example the assignments "obj.a += (2,)" and "obj.b += [2,]". These assignments work exactly as expected no matter which school of thought you come from.

The difference is in how the Class attributes are affected i.e. the values of "A.a" and "A.b".

  # obj.a += (2,) has no impact on A.a since tuples 
  # are immutable and copies are made.
  # This is unexpected from a C/C++ intuition.

  >>> print(A.a)
  (1,)    
   
  # obj.b += [2,] has an indirect impact on A.b
  # since lists are mutable and updates are made in place.
  # This is unexpected for beginners in Python since it
  # deviates from how int, str and tuples behave as "+="
  # can be used for int, str, tuples and lists.
  
  >>> print(A.b)
  [1, 2]

Edit: formatting
Re:WTFPython: Exploring and understanding Python through sur... by dragonwriter on Tuesday May 31, 2022 @06:36AM (#31567946)
> These assignments work exactly as expected no matter which school of thought you come

No, they don't.

obj.b does not exist, and no assignment to it takes place, and there is no “indirect effect”.

A.b exists, and because class attributes can be accessed as if they were members on instances, and because A.b has an implementation of in-place addition, that implementation is called on A.b instead of an assignment to obj.b. That is the only thing that happens as a result of the statement that looks like it might be an assignment to obj.b. The effect on A.b is the only action, not an indirect effect of an assignment.

Re:WTFPython: Exploring and understanding Python through sur... by masklinn on Tuesday May 31, 2022 @02:48PM (#31573302)
> The behavior of the "obj.b += [2,]" for the list is the real gotcha here where it correctly alters the value of "obj.b" but wtf'ingly also alters the value of the class attribute "A.b" which completely alters the behavior of A.

It necessarily alters the value of A.b, since `+=` is specifically overridden to update the list in-place. With lists, `a += b` is specifically not an alias for `a = a + b`, instead its behaviour is closer to

    a.extend(b)
    a = a
The issue here is the first part, I absolutely hate this override, and sadly the core team has not learned a thing there as `|=` was overridden the exact same way on dicts when `dict | dict` was added to the language.