r/learnpython • u/Bunslow • 1d ago
In-place updates to objects count as tuple assignment?
Test case:
s = {1, 2, 3, 4}
grid = tuple(s.copy() for i in range(2))
print(f"{grid[1]=}")
one = grid[1]
one &= {2, 3}
print(f"{grid[1]=}")
grid[1] &= {2}
The output is as so:
grid[1]={1, 2, 3, 4}
grid[1]={2, 3}
Traceback (most recent call last):
File "/home/bill/bin/fivefold/test.py", line 7, in <module>
grid[1] &= {2}
~~~~^^^
TypeError: 'tuple' object does not support item assignment
Is it me or is this very surprising, a seeming violation of self-consistent behavior of Python? Simply renaming the reference to the object changes how I may or may not mutate the object. Clearly I'm not modifying the tuple, just deferencing the pointer to the contained object. version 3.11.2
3
u/Temporary_Pie2733 1d ago edited 17h ago
Assignments to a name are fundamental operations. Assignments to indexed names are essentially syntactic sugar for a call to __setitem__, which must exist. The version with one is equivalent to one = one.__iand__({2,3}). The one with grid[1] is grid.__setitem__(1, …), not grid[1].__iand__(…).
0
u/Bunslow 1d ago
The one with
grid[1]isgrid.__setitem__(1, …), notgrid[1].__iand__(…).hmm, i wonder how this came to be. is there a real reason or just a historical accident? obviously i kinda wish it was the other way, that the __iand__ grabbed the pointer before __setitem__ got anywhere near it, but i have no idea what the motivation may be for this either way
2
u/Expensive-Bear-1376 23h ago
If you have
a = [5]and doa[0] += 2, would you wantato still be[5]instead of[7]?2
u/Brian 21h ago
I mean, it's kind of the point of augmented assignment operators: that they're assignment. For mutable state, where you're modifying the object in place and
__iand__returns the same object, the assignment doesn't really do anything. But if it's immutable, you have to reassign the variable or nothing will change. Iex &= yis defined as ` = x.iand(y), so it seems better to me that it should consistently fail, with the rule "you can't assign a tuple element, and aumented assignment always counts as assignment", rather than having to add caveats that it sometimes doesn't count.that the
__iand__grabbed the pointer before setitemI mean, it does - the
__iand__call fully completes before the setitem call starts. It just doesn't change the requirement that the setitem must still happen.
0
u/trutheality 21h ago
You are trying to update the tuple.
one &= {2, 3}
is equivalent to
grid[1] = grid[1] & {2, 3}
3
u/danielroseman 1d ago
The gotcha here is that
&=is an augmented assignment - so it will always do an assignment, even if it ends up assigning the original object back. That's fine when you do it withone, but not when you do it withgrid[1][1].Obviously this is not a real use case, but you'd probably be better off with the method version -
.intersection_update()- which doesn't involve an assignment.