Append to a list defined in a tuple - is it a bug?
Yes it's expected.
A tuple cannot be changed. A tuple, like a list, is a structure that points to other objects. It doesn't care about what those objects are. They could be strings, numbers, tuples, lists, or other objects.
So doing anything to one of the objects contained in the tuple, including appending to that object if it's a list, isn't relevant to the semantics of the tuple.
(Imagine if you wrote a class that had methods on it that cause its internal state to change. You wouldn't expect it to be impossible to call those methods on an object based on where it's stored).
Or another example:
>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)
Two mutable lists referenced by a list and by a tuple. Should I be able to do the last line (answer: yes). If you think the answer's no, why not? Should t
change the semantics of l3
(answer: no).
If you want an immutable object of sequential structures, it should be tuples all the way down.
Why does it error?
This example uses the infix operator:
Many operations have an “in-place” version. The following functions
provide a more primitive access to in-place operators than the usual
syntax does; for example, the statement x += y is equivalent to x =
operator.iadd(x, y). Another way to put it is to say that z =
operator.iadd(x, y) is equivalent to the compound statement z = x; z
+= y.
https://docs.python.org/2/library/operator.html
So this:
l = [1, 2, 3]
tup = (l,)
tup[0] += (4,5,6)
is equivalent to this:
l = [1, 2, 3]
tup = (l,)
x = tup[0]
x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None
tup[0] = x
The __iadd__
line succeeds, and modifies the first list. So the list has been changed. The __iadd__
call returns the mutated list.
The second line tries to assign the list back to the tuple, and this fails.
So, at the end of the program, the list has been extended but the second part of the +=
operation failed. For the specifics, see this question.
Python, is this a bug, append to a list within a tuple results in None?
list.append()
always returns None
so arf[2].append(3)
will append 3
to arf
and return None
you never get to see this change to arf[2]
because you are immediately rebinding arf
to the newly created tuple
Perhaps this is what you want
arf = (arf[0], arf[1], arf[2]+[3])
Why can you extend/append to a list in a tuple, but not assign to it?
If tuples are immutable, why is it possible to change a list within a tuple?
Because "tuples are immutable" only means you cannot modify the tuple. The list that's referred to from the tuple is not part of the tuple, it doesn't "know" that it is in a tuple, and it has no means to resist being modified.
Why does the first example raise an error and the other two not?
Because of how +=
works. It calls __iadd__
on the list and then (because __iadd__
is not required to return the original object) attempts to assign the resulting modified object back to the tuple. The first thing succeeds, the second thing fails.
That is, for this case where t[2]
has an __iadd__
function, t[2] += [5,6]
is equivalent to:
t[2] = t[2].__iadd__([5,6])
In the first example, why is the list inside the tuple changed even though an error is raised?
Because this Python operation doesn't offer what in C++ we'd call a "strong exception guarantee". The first part of the operation has already been performed, and cannot be (or at any rate is not) reversed when the second part fails. For the official version see Why does a_tuple[i] += [‘item’] raise an exception when the addition works?
Updating a list within a tuple
This is actually documented in the Python docs.
EDIT: Here's a summary so that this is a more complete answer.
- When we use
+=
, Python calls the__iadd__
magic method on the item, then uses the return value in the subsequent item assignment. - For lists,
__iadd__
is equivalent to callingextend
on the list and then returning the list. Therefore, when we call
tup[3] += [6]
, it is equivalent to:result = tup[3].__iadd__([6])
tup[3] = resultFrom #2, we can determine this is equivalent to:
result = tup[3].extend([6])
tup[3] = result- The first line succeeds in calling
extend
on the list, and since the list is mutable, it updates. However, the subsequent assignment fails because tuples are immutable, and throws the error.
Why does += of a list within a Python tuple raise TypeError but modify the list anyway?
As I started mentioning in comment, +=
actually modifies the list in-place and then tries to assign the result to the first position in the tuple. From the data model documentation:
These methods are called to implement the augmented arithmetic assignments (+=, -=, =, /=, //=, %=, *=, <<=, >>=, &=, ^=, |=). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self).
+=
is therefore equivalent to:
t[0].extend(['world']);
t[0] = t[0];
So modifying the list in-place is not problem (1. step), since lists are mutable, but assigning the result back to the tuple is not valid (2. step), and that's where the error is thrown.
Related Topics
Django Rest Framework File Upload
How to Create PDF Files in Python
How to Display Full Output in Jupyter, Not Only Last Result
Heatmap in Matplotlib with Pcolor
How to Properly Assert That an Exception Gets Raised in Pytest
Difference Between Numpy Dot() and Python 3.5+ Matrix Multiplication @
Databaseerror: Current Transaction Is Aborted, Commands Ignored Until End of Transaction Block
Share Large, Read-Only Numpy Array Between Multiprocessing Processes
Add X and Y Labels to a Pandas Plot
Python Assigning Multiple Variables to Same Value? List Behavior
Difference Between Len() and ._Len_()
How to Plot a Gradient Color Line in Matplotlib
How to Find the Groups of Consecutive Elements in a Numpy Array
Print to Standard Printer from Python
How to Pipe a Subprocess Call to a Text File
Redirecting Stdout and Stderr to a Pyqt4 Qtextedit from a Secondary Thread