Related
This question already has answers here:
a mutable type inside an immutable container
(3 answers)
Closed 6 years ago.
So I have this code:
tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
which generates this error:
TypeError: 'tuple' object does not support item assignment
While this code:
tup = ([1,2,3],[7,8,9])
try:
tup[0] += (4,5,6)
except TypeError:
print tup
prints this:
([1, 2, 3, 4, 5, 6], [7, 8, 9])
Is this behavior expected?
Note
I realize this is not a very common use case. However, while the error is expected, I did not expect the list change.
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.
Well I guess tup[0] += (4, 5, 6) is translated to:
tup[0] = tup[0].__iadd__((4,5,6))
tup[0].__iadd__((4,5,6)) is executed normally changing the list in the first element. But the assignment fails since tuples are immutables.
Tuples cannot be changed directly, correct. Yet, you may change a tuple's element by reference. Like:
>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
The Python developers wrote an official explanation about why it happens here: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
The short version is that += actually does two things, one right after the other:
Run the thing on the right.
assign the result to the variable on the left
In this case, step 1 works because you’re allowed to add stuff to lists (they’re mutable), but step 2 fails because you can’t put stuff into tuples after creating them (tuples are immutable).
In a real program, I would suggest you don't do a try-except clause, because tup[0].extend([4,5,6]) does the exact same thing.
This question already has answers here:
a mutable type inside an immutable container
(3 answers)
Closed 6 years ago.
So I have this code:
tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
which generates this error:
TypeError: 'tuple' object does not support item assignment
While this code:
tup = ([1,2,3],[7,8,9])
try:
tup[0] += (4,5,6)
except TypeError:
print tup
prints this:
([1, 2, 3, 4, 5, 6], [7, 8, 9])
Is this behavior expected?
Note
I realize this is not a very common use case. However, while the error is expected, I did not expect the list change.
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.
Well I guess tup[0] += (4, 5, 6) is translated to:
tup[0] = tup[0].__iadd__((4,5,6))
tup[0].__iadd__((4,5,6)) is executed normally changing the list in the first element. But the assignment fails since tuples are immutables.
Tuples cannot be changed directly, correct. Yet, you may change a tuple's element by reference. Like:
>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
The Python developers wrote an official explanation about why it happens here: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
The short version is that += actually does two things, one right after the other:
Run the thing on the right.
assign the result to the variable on the left
In this case, step 1 works because you’re allowed to add stuff to lists (they’re mutable), but step 2 fails because you can’t put stuff into tuples after creating them (tuples are immutable).
In a real program, I would suggest you don't do a try-except clause, because tup[0].extend([4,5,6]) does the exact same thing.
A number of Python's list methods operate in place and return None (off the top of my head, insert, sort, reverse).
However, there is one behavior that frequently frustrates me. If I create a new list, which normally returns an object, and insert on it at the same time, the new list "disappears":
mytup = (0, 1, 2, 3, 4)
print mytup # (0, 1, 2, 3, 4)
mylist = list(mytup)
print mylist # [0, 1, 2, 3, 4]
newlist = list(mytup).insert(0, 10)
print newlist # None
So if I want to modify a tuple, it requires more lines:
newlist = list(mytup)
newlist.insert(0, 10)
print newlist # [10, 0, 1, 2, 3, 4]
So I have two questions:
Is it correct to say that when I call the list constructor, it returns the object, but when I call the list constructor with a method on it, the method "overrides" the return with None? Again, where does the list go?
Is there a way to insert into a tuple and return a list in one line? I am not trying to play code golf, I just don't think the two lines are logically different enough to merit separation.
insert,sort and reverse modify the list in-place and return None. And in your code you're actually storing that returned value in the newlist variable.
newlist = list(mytup).insert(0, 10)
And that newly created list(created on the fly) is garbage collected as there are no references to it any more.
In [151]: mytup = (0, 1, 2, 3, 4)
In [152]: lis=list(mytup) #create a new list object and add a reference to this new object
In [153]: newlist=lis.insert(0,10) #perform the insert operation on lis and store None
# in newlist
In [154]: print newlist
None
In [155]: print lis
[10, 0, 1, 2, 3, 4] #you can still access this list object with
#the help of `lis` variable.
The answer to your first question has already been given; you assign to the variable the result of the last function call, which is None. Here's the answer to your second question.
Rather than using insert, do something like this:
newlist = [10] + list(mytup)
It creates a new list containing the element to be inserted, appends it to the converted tuple and stores (a reference to) the resulting list.
This, of course, only works if you want to insert on either end.
If you need the new element to be inserted somewhere else, you have to slice the tuple, e.g. to insert after the third element in the tuple:
newlist = list(mytup[:3]) + [10] + list(mytup[3:])
I am new to Python. Here is a question I have about lists:
It is said that lists are mutable and tuples are immutable. But when I write the following:
L1 = [1, 2, 3]
L2 = (L1, L1)
L1[1] = 5
print L2
the result is
([1, 5, 3], [1, 5, 3])
instead of
([1, 2, 3], [1, 2, 3])
But L2 is a tuple and tuples are immutable. Why is it that when I change the value of L1, the value of L2 is also changed?
From the Python documentation (http://docs.python.org/reference/datamodel.html), note:
The value of an immutable container object that contains a reference to a mutable
object can change when the latter’s value is changed; however the container is
still considered immutable, because the collection of objects it contains cannot
be changed. So, immutability is not strictly the same as having an unchangeable
value, it is more subtle.
The tuple is immutable, but the list inside the tuple is mutable. You changed L1 (the list), not the tuple. The tuple contains two copies of L1, so they both show the change, since they are actually the same list.
If an object is "immutable", that doesn't automatically mean everything it touches is also immutable. You can put mutable objects inside immutable objects, and that won't stop you from continuing to mutate the mutable objects.
The tuple didn't get modified, it still contains the same duplicate references to list you gave it.
You modified a list (L1), not the tuple (or more precisely, not the reference to the list in the tuple).
For instance you would not have been able to do
L2[1] = 5
because tuples are immutable as you correctly state.
So the tuple wasn't changed, but the list that the tuple contained a reference to was modified (since both entries were references to the same list, both values in the output changed to 5). No value in the tuple was changed.
It may help if you think of reference as a "pointer" in this context.
EDIT (based on question by OP in comments below):
About references, lists and copies, maybe these examples will be helpful:
L=range(5)
s = (L, L[:]) # a reference to the original list and a copy
s
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
then changing L[2]
L[2] = 'a'
gives:
s
([0, 1, 'a', 3, 4], [0, 1, 2, 3, 4]) # copy is not changed
Notice that the "2nd" list didn't change, since it contains a copy.
Now,
L=range(5)
we are creating two copies of the list and giving the references to the tuple
s = (L[:], L[:])
now
L[2] = 'a'
doesn't affect anything but the original list L
s
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
Hope this is helpful.
You're right that tuples are immutable: L2 is an immutable tuple of two references to L1 (not, as it might first appear, a tuple of two lists), and L1 is not immutable. When you alter L1, you aren't altering L2, just the objects that L2 references.
Use deepcopy instead of = :
from copy import deepcopy
L2 = deepcopy(L1)
The tuple contains two references, each to the same list (not copies of the list, as you might have expected). Hence, changes in the list will still show up in the tuple (since the tuple contains only the references), but the tuple itself is not altered. Therefore, it's immutability is not violated.
Tuples being immutable means only one thing -- once you construct a tuple, it's impossible to modify it. Lists, on the other hand, can be added elements to, removed elements from. But, both tuples and lists are concerned with the elements they contain, but not with what those elements are.
In Python, and this has nothing to do with tuples or lists, when you add a simple value, like an int, it gets represented as is, but any complex value like a list, a tuple, or any other class-type object is always stored as reference.
If you were to convert your tuple to a set(), you'd get an error message that might surprise you, but given the above it should make sense:
>>> L=range(5)
>>> s = (L, L[:]) # a reference to the original list and a copy
>>> set(1, 2, s)
>>> set((1, 2, s))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
As values of a set must never change once they are added to the set, any mutable value contained inside the immutable tuple s raises TypeError.
I have a list: v = [1,2,2,3]. I would like to use this list as a key. I can do it "manually":
x = {}
x[1,2,2,3] = 7
But
x[v] = 7
does not work. What is the easiest way to do what I need to do?
ADDED
I imagine the solution as something like that:
x[open(v)] = 7
The problem is that keys must be immutable, which lists are not. Tuples, however, are.
Simply convert v to a tuple:
x[tuple(v)] = 7
To elaborate, the above is the same as writing
x[1,2,2,3] = 7
and the latter is a syntactic variation of
x[(1,2,2,3)] = 7
Python has two similar data structures for storing lists of values. list is the mutable version: its values can be changed.
x = [1, 2, 2, 3]
x = list((1, 2, 3, 4))
tuple is the immutable version. Once created, its value can't be modified.
x = 1, 2, 2, 3
x = (1, 2, 2, 3)
x = tuple((1, 2, 2, 3))
Python doesn't let you use mutable types as dictionary keys, so you just need to create your tuple to a list:
x[tuple(v)] = 7
Dict keys must be hashable. Lists are not hashable, but tuples are. (A hash value of an object should never change during the life of the object. Moreover, two hashable objects which compare equal must have the same hash. Since lists are mutable, the only way to satisfy both conditions would be to make all lists return the same hash value. Rather than allowing this and subverting the purpose of hashes, Python make all mutable containers unhashable.)
x[tuple(v)]
x[1,2,2,3] works because a tuple is indicated by the use of commas, not the parentheses:
In [78]: 1,2,2,3
Out[78]: (1, 2, 2, 3)