I am using the PIL library.
I am trying to make an image look red-er, this is what i’ve got.
from PIL import Image
image = Image.open('balloon.jpg')
pixels = list(image.getdata())
for pixel in pixels:
pixel[0] = pixel[0] + 20
image.putdata(pixels)
image.save('new.bmp')
However I get this error: TypeError: 'tuple' object does not support item assignment
jleahy
16k6 gold badges46 silver badges66 bronze badges
asked Oct 7, 2011 at 12:52
1
PIL pixels are tuples, and tuples are immutable. You need to construct a new tuple. So, instead of the for loop, do:
pixels = [(pixel[0] + 20, pixel[1], pixel[2]) for pixel in pixels]
image.putdata(pixels)
Also, if the pixel is already too red, adding 20 will overflow the value. You probably want something like min(pixel[0] + 20, 255)
or int(255 * (pixel[0] / 255.) ** 0.9)
instead of pixel[0] + 20
.
And, to be able to handle images in lots of different formats, do image = image.convert("RGB")
after opening the image. The convert method will ensure that the pixels are always (r, g, b) tuples.
answered Oct 7, 2011 at 13:00
Petr ViktorinPetr Viktorin
65.2k9 gold badges81 silver badges81 bronze badges
2
The second line should have been pixels[0]
, with an S. You probably have a tuple named pixel
, and tuples are immutable. Construct new pixels instead:
image = Image.open('balloon.jpg')
pixels = [(pix[0] + 20,) + pix[1:] for pix in image.getdata()]
image.putdate(pixels)
answered Oct 7, 2011 at 12:54
Fred FooFred Foo
353k75 gold badges738 silver badges833 bronze badges
0
Tuples, in python can’t have their values changed. If you’d like to change the contained values though I suggest using a list:
[1,2,3]
not (1,2,3)
answered Oct 7, 2011 at 12:58
Lewis NortonLewis Norton
6,8411 gold badge19 silver badges28 bronze badges
You probably want the next transformation for you pixels:
pixels = map(list, image.getdata())
answered Oct 7, 2011 at 13:01
Roman BodnarchukRoman Bodnarchuk
29.3k12 gold badges58 silver badges75 bronze badges
A tuple is immutable and thus you get the error you posted.
>>> pixels = [1, 2, 3]
>>> pixels[0] = 5
>>> pixels = (1, 2, 3)
>>> pixels[0] = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
In your specific case, as correctly pointed out in other answers, you should write:
pixel = (pixel[0] + 20, pixel[1], pixel[2])
answered Oct 7, 2011 at 12:55
You have misspelt the second pixels
as pixel
. The following works:
pixels = [1,2,3]
pixels[0] = 5
It appears that due to the typo you were trying to accidentally modify some tuple called pixel
, and in Python tuples are immutable. Hence the confusing error message.
answered Oct 7, 2011 at 12:54
NPENPE
484k108 gold badges944 silver badges1009 bronze badges
Have you ever seen the error “tuple object does not support item assignment” when working with tuples in Python? In this article we will learn why this error occurs and how to solve it.
The error “tuple object does not support item assignment” is raised in Python when you try to modify an element of a tuple. This error occurs because tuples are immutable data types. It’s possible to avoid this error by converting tuples to lists or by using the tuple slicing operator.
Let’s go through few examples that will show you in which circumstances this error occurs and what to do about it.
Let’s get started!
Explanation of the Error “Tuple Object Does Not Support Item Assignment”
Define a tuple called cities as shown below:
cities = ('London', 'Paris', 'New York')
If you had a list you would be able to update any elements in the list.
But, here is what happens if we try to update one element of a tuple:
>>> cities[1] = 'Rome'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Tuples are immutable and that’s why we see this error.
But…
There is a workaround to this, we can:
- Convert the tuple into a list.
- Update any elements in the list.
- Convert the final list back to a tuple.
To convert the tuple into a list we will use the list() function:
>>> cities_list = list(cities)
>>> type(cities_list)
<class 'list'>
Now, let’s update the element at index 1 in the same way we have tried to do before with the tuple:
>>> cities_list[1] = 'Rome'
>>> cities_list
['London', 'Rome', 'New York']
You can see that the second element of the list has been updated.
Finally, let’s convert the list back to a tuple using the tuple() function:
>>> tuple(cities_list)
('London', 'Rome', 'New York')
Makes sense?
Avoid the “Tuple Object Does Not Support Item Assignment” Error with Slicing
The slicing operator also allows to avoid this error.
Let’s see how we can use slicing to create a tuple from our original tuple where only one element is updated.
We will use the following tuple and we will update the value of the element at index 2 to ‘Rome’.
cities = ('London', 'Paris', 'New York', 'Madrid', 'Lisbon')
Here is the result we want:
('London', 'Paris', 'Rome', 'Madrid', 'Lisbon')
We can use slicing and concatenate the first two elements of the original tuple, the new value and the last two elements of the original tuple.
Here is the generic syntax of the slicing operator (in this case applied to a tuple).
tuple_object[n:m]
This takes a slice of the tuple including the element at index n and excluding the element at index m.
Firstly, let’s see how to print the first two and last two elements of the tuple using slicing…
First two elements
>>> cities[0:2]
('London', 'Paris')
We can also omit the first zero considering that the slice starts from the beginning of the tuple.
>>> cities[:2]
('London', 'Paris')
Last two elements
>>> cities[3:]
('Madrid', 'Lisbon')
Notice that we have omitted index m considering that the slice includes up to the last element of the tuple.
Now we can create the new tuple starting from the original one using the following code:
>>> cities[:2] + ('Rome',) + cities[3:]
('London', 'Paris', 'Rome', 'Madrid', 'Lisbon')
(‘Rome’,) is a tuple with one element of type string.
Does “Tuple Object Does Not Support Item Assignment” Apply to a List inside a Tuple?
Let’s see what happens when one of the elements of a tuple is a list.
>>> values = (1, '2', [3])
If we try to update the second element of the tuple we get the expected error:
>>> values[1] = '3'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
If we try to assign a new list to the third element…
>>> values[2] = [3,4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
…once again we get back the error “‘tuple’ object does not support item assignment“.
But if we append another number to the list inside the tuple, here is what happens:
>>> values[2].append(4)
>>> values
(1, '2', [3, 4])
The Python interpreter doesn’t raise any exceptions because the list is a mutable data type.
This concept is important for you to know when you work with data types in Python:
In Python, lists are mutable and tuples are immutable.
How to Solve This Error with a List of Tuples
Do we see this error also with a list of tuples?
Let’s say we have a list of tuples that is used in a game to store name and score for each user:
users = [('John', 345), ('Mike', 23), ('Richard', 876)]
The user John has gained additional points and I want to update the points associated to his user:
>>> users[0][1] = 400
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
When I try to update his points we get back the same error we have seen before when updating a tuple.
How can we get around this error?
Tuples are immutable but lists are mutable and we could use this concept to assign the new score to a new tuple in the list, at the same position of the original tuple in the list.
So, instead of updating the tuple at index 0 we will assign a new tuple to it.
Let’s see if it works…
>>> users[0] = ('John', 400)
>>> users
[('John', 400), ('Mike', 23), ('Richard', 876)]
It does work! Once again because a list is mutable.
And here is how we can make this code more generic?
>>> users[0] = (users[0][0], 400)
>>> users
[('John', 400), ('Mike', 23), ('Richard', 876)]
Ok, this is a bit more generic because we didn’t have to provide the name of the user when updating his records.
This is just an example to show you how to address this TypeError, but in reality in this scenario I would prefer to use a dictionary instead.
It would allow us to access the details of each user from the name and to update the score without any issues.
Tuple Object Does Not Support Item Assignment Error With Values Returned by a Function
This error can also occur when a function returns multiple values and you try to directly modify the values returned by the function.
I create a function that returns two values: the number of users registered in our application and the number of users who have accessed our application in the last 30 days.
>>> def get_app_stats():
... users_registered = 340
... last_30_days_logins = 2003
... return users_registered, last_30_days_logins
...
>>> stats = get_app_stats()
>>> stats
(340, 2003)
As you can see the two values are returned by the function as a tuple.
So, let’s assume there is a new registered user and because of that I try to update the value returned by the function directly.
I get the following error…
>>> stats[0] = stats[0] + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
This can happen especially if I know that two values are returned by the function but I’m not aware that they are returned in a tuple.
Why Using Tuples If We Get This Error?
You might be thinking…
What is the point of using tuples if we get this error every time we try to update them?
Wouldn’t be a lot easier to always use lists instead?
We can see the fact that tuples are immutable as an added value for tuples when we have some data in our application that should never be modified.
Let’s say, for example, that our application integrates with an external system and it needs some configuration properties to connect to that system.
ext_system_config = ('api.ext.system.com', '443')
The tuple above contains two values: the API endpoint of the system we connect to and the port for their API.
We want to make sure this configuration is not modified by mistake in our application because it would break the integration with the external system.
So, if our code inadvertently updates one of the values, the following happens:
>>> ext_system_config[0] = 'incorrect_value'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Remember, it’s not always good to have data structures you can update in your code whenever you want.
Conclusion
In this article we have seen when the error “tuple object does not support item assignment” occurs and how to avoid it.
You have learned how differently the tuple and list data types behave in Python and how you can use that in your programs.
If you have any questions feel free to post them in the comment below 🙂
I’m a Software Engineer and Programming Coach. I want to help you in your journey to become a Super Developer!
Did you assign a tuple to a new value and get the TypeError: tuple does not support item assignment in Python?
If you’re working with Python and you encounter the “TypeError: ‘tuple’ does not support item assignment” error, it means that you are trying to change the value of an element within a tuple, which is not possible.
When we try to update the value of an item in a tuple, Python throws the error TypeError: ‘tuple’ object does not support item assignment. Tuples are immutable data types, hence assigning a tuple to a variable or data causes the TypeError exception. Transforming tuples to lists or slicing them, can be helpful in preventing the TypeError: ‘tuple’ object does not support item assignment.
Furthermore, you can convert the tuple to a list, to make the necessary changes, and then convert the list back to a tuple to fix the problem.
In this article, we’ll discuss the TypeError: tuple object does not support item assignment in Python, why it occurs, and how to fix ⚒️it. So without further ado, let’s dive deep into the topic. Let’s go over a few examples that will show this error’s causes and possible solutions.
Table of Contents
- Why Does The TypeError: Tuple Does Not Support Item Assignment Error Occur?
- How to Fix TypeError: ‘Tuple’ Object Does Not Support Item Assignment in Python?
- 1. Assigning a Value to the Index
- 2. With the Use of Slice Operator
- 3. Apply List Inside a Tuple
Why Does The TypeError: Tuple Does Not Support Item Assignment Error Occur?
As we’ve discussed in Python, when we try to assign a new value to a tuple that causes TypeError: tuple object does not support item assignment. Let’s see an example 👇
Code
Tuple_1 = ("Charles", "Danial", "Henry") Tuple_1[2] = "Kelvin" print(Tuple_1)
Output
See the above example; we have created a tuple Tuple_1 and assigned values. Then we assigned “Kelvin” at index 2 of Tuple and print the tuple that gives the TypeError: tuple does not support item assignment as we are trying to assign a value to an already created tuple.
Code
Tuple_1 = ("Charles", "Danial", "Henry") Tuple_new_list = list(Tuple_1) Tuple_new_list='1' print(Tuple_new_list)
Output
1
As we have seen in the above example, we have created a tuple and assigned a value, we can convert the tuple into a list, and then we can assign values to it. To convert a tuple into a list, we utilized the list() class. In the above example, we have assigned 1.
To fix the error first we have to change the tuple to a list: we have three different alternate solutions.
- Assigning a Value to the Index
- With the Use of Slice Operator
- Apply List Inside a Tuple
1. Assigning a Value to the Index
We have to convert Convert the tuple into a list by using a list function and then assigning a value at any index of a list that will update any elements in the list. The final step is to convert
final list back to a tuple as shown in the following example.
Code
Tuple_1 = ("Charles", "Danial", "Henry") print('Tuple before assigning a value:',Tuple_1) Tuple_new_list = list(Tuple_1) Tuple_new_list[1] = 'Sofia' Tuple_new = tuple(Tuple_new_list) print('Updated Tuple:', Tuple_new)
Output
Tuple before assigning a value: ('Charles', 'Danial', 'Henry') Updated Tuple: ('Charles', 'Sofia', 'Henry')
In the above example, we have converted the tuple to a list, assigned “Sofia” at the index on the list, and again converted the list to a tuple and printed it.
2. With the Use of Slice Operator
This “Type error: tuple object does not support item assignment” can also be avoided using the slicing operator. Let’s look at how we can slice our original tuple to get a new one after omitting some elements of the tuple. You can also add an element to any index after in the tuple using the slice operator.
Code
Tuple_1 = ("Charles", "Danial", "Henry","Hanna") print('Tuple :',Tuple_1) Tuple_new_list = list(Tuple_1) print('Tuple after slicing : ',Tuple_new_list[2:])
Output
Tuple : ('Charles', 'Danial', 'Henry', 'Hanna') Tuple after slicing : ['Henry', 'Hanna']
3. Apply List Inside a Tuple
If one element in a tuple is listed, only on that particular index we can assign another element. But if we assign an element at the index of an element that is not a list it will generate a “Type error: tuple object does not support item assignment.” Let’s see what happens when a tuple has a list as one of its elements.
Code
Tuple_1 = ("Charles", "Danial", "Henry",["Hanna"]) Tuple_1[3].append("Sofia") print("Updated Tuple:", Tuple_1)
Output
Updated Tuple: ('Charles', 'Danial', 'Henry', ['Hanna', 'Sofia'])
Conclusion
To summarize the article on how to fix the TypeError: tuple does not support item assignment, we’ve discussed why it occurs and how to fix it. Furthermore, we’ve seen that the three approaches that help fix the TypeError: ‘tuple’ object do not support item assignment, including Assigning a value to the index, With the use of slice Operator, Applying a list inside a tuple
Let’s have a quick recap of the topics discussed in this article.
- Why does the TypeError: ‘tuple’ object does not support item assignment occurs?
- How to fix the TypeError TypeError: tuple does not support item assignment in Python?
- Assigning a value to the index.
- With the use of slice Operator.
- Apply List inside a Tuple.
If you’ve found this article helpful, don’t forget to share and comment below 👇 which solutions have helped you solve the problem.
Happy Coding!
Here’s everything about TypeError: ‘Tuple’ Object Does Not Support Item Assignment in Python.
You’ll learn:
- The specifics of the tuple data type
- The difference between immutable and mutable data types
- How to change immutable data types
- Lots more
So if you want to understand this error in Python and how to solve it, then you’re in the right place.
Let’s jump right in!
Mutable, or Immutable? That Is the Question
Data types in Python are mutable or immutable.
All data types that are numeric, for example, are immutable.
You can write something like this:
a = 1
a
1
And:
a = a + 1
a
2
Have you changed the variable a?
Not really: When you write a = 1, you put the object 1 in memory and told the name a to refer to this literal.
Next, when you write a = a + 1, Python evaluates the expression on the right:
Python takes the object referred by a (the 1) and then adds 1 to it.
You get a new object, a 2. This object goes right into the memory and a references instead of object 1.
The value of object 1 has not changed—it would be weird if 1 would out of a sudden a 2, for example, wouldn’t it? So instead of overwriting an object (1), a new object (2) is created and assigned to the variable (a).
Mutable Data Types
More complex data types in Python are sequences such as:
- Strings
- Tuples
- Bytes
- Lists
- Byte Arrays
Sequences contain several values, which can be accessed by index.
However, some sequences are mutable (byte arrays, lists), while others are immutable (tuples).
You can create a tuple and access its elements like this:
tup1 = (1, "two", [3])
tup1[1]
two
Yet if you try to change one of the elements, you get an error:
tup1[1] = '2'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-903ae2cb95b0> in <module>()
----> 1 tup1[1] = '2'
TypeError: 'tuple' object does not support item assignment
Notice that the item in the tuple at index 2 is a list. You can change the list without changing the tuple:
tup1[2].append(4)
tup1
(1, 'two', [3, 4])
The object stored in the tuple remains the same, but its contents have changed. But what if you still need to change the element in the tuple?
You can do this by converting the tuple to a list. Then you change the element, and then convert the list to a tuple again:
tup1 = list(tup1)
tup1[0] = 'uno'
tup1 = tuple(tup1)
tup1
('uno', 'two', [3, 4])
For large amounts of data, conversion operations can take quite a long time:
import random
import time
tup2 = tuple(random.random() for _ in range(100_000_000))
t = time.process_time()
tup2 = list(tup2)
elapsed_time = time.process_time() - t
print('tuple->list: ', elapsed_time)
tup2[0] = random.random()
t = time.process_time()
tup2 = tuple(tup2)
elapsed_time = time.process_time() - t
print('list->tuple: ', elapsed_time)
tuple->list: 0.8301777420000036
list->tuple: 0.9393838999999957
As you can see, for a list of 100 million float numbers, this operation takes about a second. This is not a long time for most tasks, but it is still worth considering if you are dealing with large amounts of data.
However, there is another way to “change” a tuple element—you can rebuild a tuple using slicing and concatenation:
tup1 = (1, "two", [3])
tup1 = ('uno',) + tup1[1:]
tup1
Note that it is necessary to put a comma in parentheses to create a tuple of one element. If you use just parentheses, then (‘uno’) is not a tuple, but a string in parentheses.
Concatenating a string with a tuple is not possible:
tup1 = ('uno') + tup1[1:]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-c51d4ed19b5a> in <module>()
----> 1 tup1 = ('uno') + tup1[1:]
TypeError: must be str, not tuple
Interestingly, you can use shorthand operators on a tuple, like this:
tup = (1, 2)
tup += (3, 4, 5)
tup
(1, 2, 3, 4, 5)
Or even like this:
tup = (1, 2)
tup *= 3
tup
(1, 2, 1, 2, 1, 2)
3 Examples of TypeError: ‘Tuple’ Object Does Not Support Item Assignment in Python
Let’s look at some practical examples of when this error can occur. The simplest is when you initially enter the sequence incorrectly:
list1 = (1, 2, 3)
list1[0] = 'one'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-da9ebc4ef661> in <module>()
1 list1 = (1, 2, 3)
----> 2 list1[0] = 'one'
TypeError: 'tuple' object does not support item assignment
In this example, the name list1 refers to a tuple despite the list in the name. The name does not affect the type of variable. To fix this error, simply change the parentheses to square brackets in the constructor:
list1 = [1, 2, 3]
list1[0] = 'one'
list1
['one', 2, 3]
Perhaps you have a list with some values, such as the student’s name and grade point average:
grades = [('Alice', 98), ('Bob', 65), ('Carol', 87)]
Alice did a poor job this semester, and her GPA dropped to 90:
grades[0][1] = 90
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-dc371b5fb12e> in <module>()
----> 1 grades[0][1] = 90
TypeError: 'tuple' object does not support item assignment
Unfortunately, you cannot just change the average score in such a list. You already know that you can convert a tuple to a list, or form a new tuple. For example, like this:
grades[0] = (grades[0][0], 90)
grades
[('Alice', 90), ('Bob', 65), ('Carol', 87)]
However, if you need to change values regularly, it makes sense to switch from a list of tuples to a dictionary. Dictionaries are a perfect fit for such tasks. You can do this easily with the dict() constructor:
grades = [('Alice', 98), ('Bob', 65), ('Carol', 87)]
grades = dict(grades)
grades
{'Alice': 98, 'Bob': 65, 'Carol': 87}
Now you can change the average by student name:
grades['Alice'] = 90
grades
{'Alice': 90, 'Bob': 65, 'Carol': 87}
#1 Real World Example of TypeError: ‘Tuple’ Object Does Not Support Item Assignment in Python
An interesting example of a novice programmer trying to enter values in a list from the keyboard using the eval() function:
def my_sort(list):
for index in range(1, len(list)):
value = list[index]
i = index-1
while i>=0:
if value <list[i]:
list[i +1] = list[i]
list[i] = value
i = i-1
else:
break
return
input_list = eval(input("Enter list items:"))
my_sort (input_list)
print(input_list)
Enter list items:3, 2, 4, 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-16-a1ba042b65c4> in <module>()
13
14 input_list = eval(input("Enter list items:"))
---> 15 my_sort (input_list)
16 print(input_list)
<ipython-input-16-a1ba042b65c4> in my_sort(list)
5 while i>=0:
6 if value <list[i]:
----> 7 list[i +1] = list[i]
8 list[i] = value
9 i = i-1
TypeError: 'tuple' object does not support item assignment
This method is not very reliable by itself.
Even if the user enters the correct sequence separated by commas—for example, 3, 2, 4, 1—it will be evaluated in a tuple.
Naturally, an attempt to assign a new value to a tuple element in the line list[i +1] = list[i] raises a TypeError: ‘tuple’ object does not support item assignment.
Here, you see another mistake—which, by the way, may even be invisible during program execution.
The my_sort function uses the list data type name as the argument name. This is not only the name of the data type, but also the list constructor.
Python will not throw an error while executing this code, but if you try to create a list using the constructor inside the my_sort function, you will have big problems.
In this case, to enter elements into the list, it would be more correct to read the entire string and then split it using the split() method. If you need integer values, you can also apply the map() function, then convert the resulting map object into a list:
input_list = list (map(int, input("Enter the list items: ") .split ()))
input_list
Enter the list items: 4 2 3 1
[4, 2, 3, 1]
The construction looks a little cumbersome, but it does its job. You can also enter list items through a list comprehension:
input_list = [int (x) for x in input("Enter the list items: ") .split ()]
input_list
Enter the list items: 4 2 3 1
[4, 2, 3, 1]
You can choose the design that you like best.
#2 Real World Example of TypeError: ‘Tuple’ Object Does Not Support Item Assignment in Python
Another example of when a TypeError: ‘tuple’ object does not support item assignment may occur is the use of various libraries.
If you have not studied the documentation well enough, you may not always clearly understand which data type will be returned in a given situation. In this example, the author tries to make the picture redder by adding 20 to the red color component:
from PIL import Image
image = Image.open('balloon.jpg')
pixels = list (image.getdata ())
for pixel in pixels:
pixel[0] = pixel[0] + 20
image.putdata(pixels)
image.save('new.bmp')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-23-2e305d7cf9e6> in <module>()
3 pixels = list (image.getdata ())
4 for pixel in pixels:
----> 5 pixel[0] = pixel[0] + 20
6 image.putdata(pixels)
7 image.save('new.bmp')
TypeError: 'tuple' object does not support item assignment
This produces an error on the line pixel[0] = pixel[0] + 20. How?
You are converting pixels to a list in line of code 3. Indeed, if you check the type of the pixels variable, you get a list:
type(pixels)
list
However, in the loop, you iterate over the pixels list elements, and they already have a different type. Check the type of the pixels list element with index 0:
type(pixels[0])
tuple
And this is a tuple!
So, you can solve this problem by converting lists to tuples inside a loop, for example.
However, in this case, you will need to slightly adjust the iterable value. This is because you will need the pixel color values and the index to write the new values into the original array.
For this, use the enumerate() function:
for i, pixel in enumerate(pixels):
pixel = list (pixel)
pixel [0] = pixel [0] + 20
pixels [i] = tuple (pixel)
The program will work successfully with that version of a loop, and you will get a redder image at the output. It would be more correct to trim values above 255, for example:
pixel[0] = min(pixel [0] + 20, 255)
But if the program consists only of this transformation, then Python will already truncate the values when saving the image.
Here’s more Python support:
- 9 Examples of Unexpected Character After Line Continuation Character
- 3 Ways to Solve Series Objects Are Mutable and Cannot be Hashed
- How to Solve SyntaxError: Invalid Character in Identifier
- ImportError: Attempted Relative Import With No Known Parent Package
- IndentationError: Unexpected Unindent in Python (and 3 More)
Время на прочтение
3 мин
Количество просмотров 20K
В языках программирования меня всегда интересовало их внутреннее устройство. Как работает тот или иной оператор? Почему лучше писать так, а не иначе? Подобные вопросы не всегда помогают решить задачу «здесь и сейчас», но в долгосрочной перспективе формируют общую картину языка программирования. Сегодня я хочу поделиться результатом одного из таких погружений и ответить на вопрос, что происходит при модификации tuple
‘а в list
‘е.
Все мы знаем, что в Python есть тип данных list
:
a = []
a.append(2)
list
— это просто массив. Он позволяет добавлять, удалять и изменять элементы. Также он поддерживает много разных интересных операторов. Например, оператор += для добавления элементов в list
. +=
меняет текущий список, а не создает новый. Это хорошо видно тут:
>>> a = [1,2]
>>> id(a)
4543025032
>>> a += [3,4]
>>> id(a)
4543025032
В Python есть еще один замечательный тип данных: tuple
— неизменяемая коллекция. Она не позволяет добавлять, удалять или менять элементы:
>>> a = (1,2)
>>> a[1] = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
При использовании оператора +=
создается новый tuple
:
>>> a = (1,2)
>>> id(a)
4536192840
>>> a += (3,4)
>>> id(a)
4542883144
Внимание, вопрос: что сделает следующий код?
a = (1,2,[3,4])
a[2] += [4,5]
Варианты:
- Добавятся элементы в список.
- Вылетит исключение о неизменяемости tuple.
- И то, и другое.
- Ни то, ни другое.
Запишите свой ответ на бумажке и давайте сделаем небольшую проверку:
>>> a = (1,2,[3,4])
>>> a[2] += [4,5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Ну что же! Вот мы и разобрались! Правильный ответ — 2. Хотя, подождите минутку:
>>> a
(1, 2, [3, 4, 4, 5])
На самом деле правильный ответ — 3. То есть и элементы добавились, и исключение вылетело — wat?!
Давайте разберемся, почему так происходит. И поможет нам в этом замечательный модуль dis
:
import dis
def foo():
a = (1,2,[3,4])
a[2] += [4,5]
dis.dis(foo)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 LOAD_CONST 4 (4)
12 BUILD_LIST 2
15 BUILD_TUPLE 3
18 STORE_FAST 0 (a)
3 21 LOAD_FAST 0 (a)
24 LOAD_CONST 2 (2)
27 DUP_TOP_TWO
28 BINARY_SUBSCR
29 LOAD_CONST 4 (4)
32 LOAD_CONST 5 (5)
35 BUILD_LIST 2
38 INPLACE_ADD
39 ROT_THREE
40 STORE_SUBSCR
41 LOAD_CONST 0 (None)
44 RETURN_VALUE
Первый блок отвечает за построение tuple
‘а и его сохранение в переменной a
. Дальше начинается самое интересное:
21 LOAD_FAST 0 (a)
24 LOAD_CONST 2 (2)
Загружаем в стек указатель на переменную a
и константу 2.
27 DUP_TOP_TWO
Дублируем их и кладем в стек в том же порядке.
28 BINARY_SUBSCR
Этот оператор берет верхний элемент стека (TOS) и следующий за ним (TOS1). И записывает на вершину стека новый элемент TOS = TOS1[TOS]
. Так мы убираем из стека два верхних значения и кладем в него ссылку на второй элемент tuple
‘а (наш массив).
29 LOAD_CONST 4 (4)
32 LOAD_CONST 5 (5)
35 BUILD_LIST 2
Строим список из элементов 4 и 5 и кладем его на вершину стека:
38 INPLACE_ADD
Применяем +=
к двум верхним элементам стека (Важно! Это два списка! Один состоит из 4 и 5, а другой взяты из tuple
). Тут всё нормально, инструкция выполняется без ошибок. Поскольку +=
изменяет оригинальный список, то список в tuple
‘е уже поменялся (именно в этот момент).
39 ROT_THREE
40 STORE_SUBSCR
Тут мы меняем местами три верхних элемента стека (там живет tuple
, в нём индекс массива и новый массив) и записываем новый массив в tuple
по индексу. Тут-то и происходит исключение!
Ну что же, вот и разобрались! На самом деле список менять можно, а падает всё на операторе =
.
Давайте напоследок разберемся, как переписать этот код без исключений. Как мы уже поняли, надо просто убрать запись в tuple
. Вот парочка вариантов:
>>> a = (1,2,[3,4])
>>> b = a[2]
>>> b += [4,5]
>>> a
(1, 2, [3, 4, 4, 5])
>>> a = (1,2,[3,4])
>>> a[2].extend([4,5])
>>> a
(1, 2, [3, 4, 4, 5])
Спасибо всем, кто дочитал до конца. Надеюсь, было интересно =)
UPD. Коллеги подсказали, что этот пример так же разобран в книге Fluent Python Лучано Ромальо. Очень рекомендуют ее почитать всем заинтересованным