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 Tech Lead, Software Engineer and Programming Coach. I want to help you in your journey to become a Super Developer!
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
15.7k5 gold badges45 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
64.6k9 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
351k75 gold badges730 silver badges828 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,7911 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
29k12 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
479k105 gold badges940 silver badges1006 bronze badges
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)
The TypeError: ‘tuple’ object does not support item assignment error occurs when you try to change the value in the tuple by using the item assignment operator that does not support. The python tuple is an immutable object. If a tuple has been created, you can’t change the tuple. If you attempt to change the value of the tuple, the error TypeError: ‘tuple’ object does not support item assignment will be thrown in python.
A tuple is a collection of python objects. If a tuple has been created, the elements in the tuple can not be modified. If you want to alter the tuple, a new tuple will be created with the changes. After creating a new tuple, the original tuple would be removed. The Error TypeError: ‘tuple’ object does not support item assignment will be thrown if the current tuple is changed by the assignment operator
You can read the elements in the tuple using the tuple index. If the tuple is changed using an index value, the error TypeError: ‘tuple’ object does not support item assignment will be thrown. Tuple will not allow any element to be added, removed or changed.
Exception
The error TypeError: ‘tuple’ object does not support item assignment will be shown as below the stack trace. The stack trace will display the line that the assignment operator is attempting to change a value in the tuple.
Traceback (most recent call last):
File "/Users/python/Desktop/test.py", line 2, in <module>
x[0] = 10
TypeError: 'tuple' object does not support item assignment
[Finished in 0.1s with exit code 1]
How to reproduce this issue
If you try to change an element in a tuple using the assignment operator, this error can be repeated. In the example below, an attempt is made to change the value in index 0 in the tuple using the assignment operator. That is why the error will be thrown.
x = (1,2,3)
x[0] = 10
print x
Output
Traceback (most recent call last):
File "/Users/python/Desktop/test.py", line 2, in <module>
x[0] = 10
TypeError: 'tuple' object does not support item assignment
[Finished in 0.1s with exit code 1]
Solution 1
If you need to change values in a tuple, the tuple will be converted to a list of values, and then the value will be changed and converted back to a tuple. The list is a mutable object, which can be modified at any time. The tuple is an immutable object that can not be modified once it has been created. Therefore, before changing the values, the tuple is converted to a list.
x = (1,2,3)
y = list(x)
y[0] = 10
x = tuple(y)
print x
Output
(10, 2, 3)
[Finished in 0.1s]
Solution 2
If you need to change a value in a tuple, converting it to a list and converting it back to a tuple is not a good choice. The tuple slice is used to split and incorporate changes to the tuple. The tuple is an immutable object that can not be modified once it has been created. The tuple slice is therefore a choice to change a value.
x = (1,2,3)
index = 1
x = x[:index] + (10,) + x[index + 1:]
print x
Output
(1, 10, 3)
[Finished in 0.1s]
Solution 3
The tuple is an immutable object that can not be modified once it has been created. If you need to change the values later in time, create a list instead of a tuple. The list is a mutable object, which can be modified at any time. At any time, you can convert to a tuple.
x = [1,2,3]
x[0] = 10
print x
x = tuple(x)
print x
Output
[10, 2, 3]
(10, 2, 3)
[Finished in 0.1s]
Solution 4
If you are not sure about the object, check the object first. If the object is a mutable object, change the value of the object. Otherwise, take an alternate flow of the code.
x = (1,2,3)
if type(x) is tuple :
x = list(x)
x[0] = 10
print x
Output
(1, 2, 3)
[10, 2, 3]
[Finished in 0.1s]
Tuples are immutable objects. “Immutable” means you cannot change the values inside a tuple. You can only remove them. If you try to assign a new value to an item in a variable, you’ll encounter the “typeerror: ‘tuple’ object does not support item assignment” error.
In this guide, we discuss what this error means and why you may experience it. We’ll walk through an example of this error so you can learn how to solve it in your code.
Find Your Bootcamp Match
- Career Karma matches you with top tech bootcamps
- Access exclusive scholarships and prep courses
Select your interest
First name
Last name
Phone number
By continuing you agree to our Terms of Service and Privacy Policy, and you consent to receive offers and opportunities from Career Karma by telephone, text message, and email.
typeerror: ‘tuple’ object does not support item assignment
While tuples and lists both store sequences of data, they have a few distinctions. Whereas you can change the values in a list, the values inside a tuple cannot be changed. Also, tuples are stored within parenthesis whereas lists are declared between square brackets.
Because you cannot change values in a tuple, item assignment does not work.
Consider the following code snippet:
honor_roll = ["Bill", "Jeff", "Lucy", "Lindsay"] honor_roll[0] = "Holly"
This code snippet lets us change the first value in the “honor_roll” list to Holly. This works because lists are mutable. You can change their values. The same code does not work with data that is stored in a tuple.
An Example Scenario
Let’s build a program that tracks the courses offered by a high school. Students in their senior year are allowed to choose from a class but a few classes are being replaced.
Start by creating a collection of class names:
classes = ("Chemistry", "Politics", "Biology", "Psychology")
We’ve created a tuple that stores the names of each class being offered.
The science department has notified the school that psychology is no longer being offered due to a lack of numbers in the class. We’re going to replace psychology with philosophy as the philosophy class has just opened up a few spaces.
To do this, we use the assignment operator:
classes[3] = "Philosophy"
This code will replace the value at the index position 3 in our list of classes with “Philosophy”. Next, we print our list of classes to the console so that the user can see what classes are being actively offered:
print("The classes being offered are: ") for c in classes: print(c)
Use a for loop to print out each class in our tuple to the console. Let’s run our code and see what happens:
Traceback (most recent call last): File "main.py", line 3, in <module> classes[3] = "Philosophy" TypeError: 'tuple' object does not support item assignment
Our code returns an error.
The Solution
We’ve tried to use the assignment operator to change a subject in our list. Tuples are immutable so we cannot change their values. This is why our code returns an error.
To solve this problem, we convert our “classes” tuple into a list. This will let us change the values in our sequence of class names.
Do this using the list()
method:
classes = ("Chemistry", "Politics", "Biology", "Psychology") as_list = list(classes) as_list[3] = "Philosophy" print("The classes being offered are: ") for c in as_list: print(c)
We use the list()
method to convert the value of “classes” to a list. We assign this new list to the variable “as_list”. Now that we have our list of classes stored as a list, we can change existing classes in the list.
Let’s run our code:
The classes being offered are: Chemistry Politics Biology Philosophy
Our code successfully changes the “Psychology” class to “Philosophy”. Our code then prints out the list of classes to the console.
If we need to store our data as a tuple, we can always convert our list back to a tuple once we have changed the values we want to change. We can do this using the tuple()
method:
as_tuple = tuple(as_list) print(as_tuple)
This code converts “as_list” to a tuple and prints the value of our tuple to the console:
('Chemistry', 'Politics', 'Biology', 'Philosophy')
We could use this tuple later in our code if we needed our class names stored as a tuple.
Conclusion
The “typeerror: ‘tuple’ object does not support item assignment” error is raised when you try to change a value in a tuple using item assignment.
To solve this error, convert a tuple to a list before you change the values in a sequence. Optionally, you can then convert the list back to a tuple.
Now you’re ready to fix this error in your code like a pro!
Некоторые не совсем очевидные вещи, с которыми сталкиваются начинающие программисты Python.
Почему я получаю исключение UnboundLocalError, хотя переменная имеет значение?
Может показаться неожиданным получить UnboundLocalError в ранее работающем коде, в который добавили операцию присваивания где-то внутри функции.
Этот код:
>>> x = 10 >>> def bar(): ... print(x) >>> bar() 10
работает, но следующий код:
>>> x = 10 >>> def foo(): ... print(x) ... x += 1
приводит к UnboundLocalError:
>>> foo() Traceback (most recent call last): ... UnboundLocalError: local variable 'x' referenced before assignment
Это происходит потому, что, когда вы делаете присваивание переменной в области видимости, она становится локальной в этой области и скрывает другие переменные с таким же именем во внешних областях.
Когда последняя инструкция в foo присваивает новое значение переменной x, компилятор решает, что это локальная переменная. Следовательно, когда более ранний print пытается напечатать неинициализированную переменную, возникает ошибка.
В примере выше можно получить доступ к переменной, объявив её глобальной:
>>> x = 10 >>> def foobar(): ... global x ... print(x) ... x += 1 >>> foobar() 10
Это явное объявление требуется для того, чтобы напомнить вам, что (в отличие от внешне аналогичной ситуации с переменными класса и экземпляра), вы на самом деле, изменяете значение переменной во внешней области видимости:
>>> print(x) 11
Вы можете сделать подобную вещь во вложенной области видимости использованием ключевого слова nonlocal:
>>> def foo(): ... x = 10 ... def bar(): ... nonlocal x ... print(x) ... x += 1 ... bar() ... print(x) >>> foo() 10 11
В Python, переменные, на которые только ссылаются внутри функции, считаются глобальными. Если переменной присваивается новое значение где-либо в теле функции, считается, что она локальная, и, если вам нужно, то нужно явно указывать её глобальной.
Хотя это немного удивительно на первый взгляд, это легко объяснимо. С одной стороны, требование global для присваиваемых переменных предотвращает непреднамеренные побочные эффекты в bar. С другой стороны, если global был обязательным для всех глобальных ссылок, вы бы использовали global все время. Вы должны были бы объявить как глобальную каждую ссылку на встроенную функцию или компонент импортируемого модуля.
Почему анонимные функции (lambda), определенные в цикле с разными значениями, возвращают один и тот же результат?
Например, вы написали следующий код:
>>> squares = [] >>> for x in range(5): ... squares.append(lambda: x**2)
Это даёт вам список из 5 функций, считающих x**2. Можно ожидать, что, будучи вызванными, они вернут, соответственно, 0, 1, 4, 9, и 16. Однако, вы увидите, что все они возвращают 16:
>>> squares[2]() 16 >>> squares[4]() 16
Это случается, поскольку x не является локальной для lambda, а определена во внешней области видимости, и получается тогда, когда она вызывается — а не когда определяется.
В конце цикла, x=4, поэтому все функции возвращают 4**2, то есть 16. Это можно также проверить, изменив значение x и посмотрев на результат:
>>> x = 8 >>> squares[2]() 64
Чтобы избежать подобного, необходимо сохранять значения переменных локально:
>>> squares = [] >>> for x in range(5): ... squares.append(lambda n=x: n**2)
Здесь, n=x создаёт локальную для функции переменную n и вычисляется в момент определения функции:
>>> squares[2]() 4 >>> squares[4]() 16
Это применимо не только к анонимным, а также и к обычным функциям.
Как организовать совместный доступ к глобальным переменным для нескольких модулей?
Канонический способ организовать подобный доступ — это создать отдельный модуль (часто называемый config или cfg). Просто добавьте import config в каждый модуль приложения. При этом модуль становится доступен через глобальное имя. Поскольку существует только один экземпляр модуля, любые изменения, произведённые в модуле отражаются везде. Например:
config.py:
x = 0
mod.py:
import config config.x = 1
main.py:
import config import mod print(config.x)
По тем же соображениям, модули можно использовать как основу для имплементации синглтона.
Как правильнее использовать импортирование?
В общих случаях не используйте from modulename import *. Это засоряет пространство имён того, кто импортирует. Некоторые люди избегают этой идиомы даже для тех немногих модулей, которые были спроектированны, чтобы так импортироваться. Это такие модули как Tkinter и threading.
Импортируйте модули в начале файла. Это отвечает на вопрос, какие модули требует Ваш код и находится ли имя модуля в области видимости. Запись по одному импорту на строку упрощает добавление и удаление операторов импорта, но множественный импорт будет занимать меньше места на экране.
Хорошая практика, если Вы импортируете модули в следующем порядке:
- стандартные библиотечные модули (например, sys, os, getopt, re)
- модули сторонних разработчиков (всё, что установлено в директории site-packages) — например, PIL, NumPy и т.д.
- локально созданные модули
Иногда бывает необходимо поместить импорт в функцию или класс, чтобы избежать проблем с циклическим импортом. Gordon McMillan советует:
Циклический импорт отлично работает, если оба модуля используют форму
import <module>
. Но они терпят неудачу, когда второй модуль хочет извлечь имя из первого (
from module import name
) и импорт находится на внешнем уровне. Это происходит из-за того, что имена первого модуля ещё недоступны, так как первый модуль занят импортом второго.
В этом случае, если второй модуль используется только в одной функции, то импорт можно легко поместить в эту функцию. К тому времени, как он будет вызван, первый модуль уже закончит инициализацию и второй модуль осуществит свой импорт.
Может оказаться необходимым переместить импорт из начала файла, если один из модулей платформно-зависимый. В этом случае импорт всех модулей в начале файла окажется невозможным. В этой ситуации хорошим решением будет импорт нужных модулей в соответствующем платформно-зависимом коде.
Переносите импорт во вложенные области видимости, такие как определения функций, только если Вы столкнулись с проблемой, например циклического импорта, или если Вы пытаетесь сократить время инициализации модуля.
Эта техника полезна, если многие из импортов не являются необходимыми, и зависят от того, как программа будет исполняться. Вы также можете поместить импорт в функцию, если конкретные модули используются только в этой функции. Обратите внимание, что загрузить модуль в первый раз может оказаться дорого из-за задержки на инициализацию модуля, однако повторные загрузки «бесплатны», они стоят только пары поисков в словарях. Даже если имя модуля исчезло из области видимости, модуль скорее всего до сих пор находится в sys.modules.
Почему значения по умолчанию разделяются между объектами?
Этот тип ошибки часто встречается среди начинающих. Предположим, функция:
def foo(mydict={}): # Опасность: разделяемая ссылка между вызовами ... compute something ... mydict[key] = value return mydict
В первый раз, когда вы вызываете функцию, mydict содержит одно значение. Второй раз, mydict содержит 2 элемента, поскольку, когда foo() начинает выполняться, mydict уже содержит элемент.
Часто ожидается, что вызов функции создаёт новые объекты для значений по умолчанию. Но это не так. Значения по умолчанию создаются лишь однажды, когда функция определяется. Если этот объект изменяется, как словарь в нашем примере, последующие вызовы функции будут использовать изменённый объект.
По определению, неизменяемые объекты (числа, строки, кортежи и None), безопасны при изменении. Изменение изменяемых объектов, таких как словари, списки, и экземпляры пользовательских классов может привести к неожиданным последствиям.
Поэтому, хорошей практикой является не использовать изменяемые объекты в качестве значений по умолчанию. Вместо этого, используйте None и внутри функции, проверяйте аргумент на None и создавайте новый список/словарь. Например, не пишите:
def foo(mydict={}): ...
Но пишите так:
def foo(mydict=None): if mydict is None: mydict = {} # create a new dict for local namespace
Однако, эта особенность может быть полезна. Когда у вас есть функция, которая долго выполняется, часто применяемая техника — кэширование параметров и результата каждого вызова функции:
def expensive(arg1, arg2, _cache={}): if (arg1, arg2) in _cache: return _cache[(arg1, arg2)] # Расчёт значения result = ... expensive computation ... _cache[(arg1, arg2)] = result # Кладём результат в кэш return result
Как передать опциональные или именованные параметры из одной функции в другую?
Получить такие параметры можно с помощью спецификаторов * и ** в списке аргументов функции; они возвращают кортеж позиционных аргументов и словарь именованых параметров. После этого Вы можете передать их в другую функцию, используя в её вызове * и **:
def f(x, *args, **kwargs): ... kwargs['width'] = '14.3c' ... g(x, *args, **kwargs)
Почему изменение списка ‘y’ изменяет также список ‘x’?
Если вы написали код:
>>> x = [] >>> y = x >>> y.append(10) >>> y [10] >>> x [10]
вы, возможно, будете удивлены тому, что добавление в y изменяет также и x.
Два факта приводят к такому результату:
- Переменные — это просто ссылки на объекты. y = x не создаёт копию списка — это просто создаёт переменную y, которая ссылается на тот же объект, что и x.
- Списки изменяемы.
После вызова append, содержимое объекта было изменено с [] на [10]. Поскольку x и y ссылаются на один и тот же объект, использование любого из них даёт нам [10].
Если мы используем неизменяемые объекты:
>>> x = 5 # числа неизменяемы >>> y = x >>> x = x + 1 # 5 нельзя изменить. Мы создаём НОВЫЙ объект >>> x 6 >>> y 5
мы можем видеть, что x и y больше не равны, поскольку числа неизменяемы, и x = x + 1 не изменяет число 5 путем увеличения. Вместо этого, создаётся новый объект 6 и присваивается переменной x (то есть, изменяется то, на какой объект ссылается x). После этого у нас 2 объекта (6 и 5) и 2 переменные, которые на них ссылаются.
Некоторые операции (например y.append(10) и y.sort()) изменяют объект, в то время, как внешне похожие операции (например y = y + [10] и sorted(y)) создают новый объект. Вообще в Python (и во всех случаях в стандартной библиотеке), метод, который изменяет объект, возвращает None, чтобы помочь избежать ошибок. Поэтому, если вы написали
y = y.sort()
думая, что это даст вам отсортированную копию y, вы вместо этого получите None, что скорее всего приведёт к легко диагностируемой ошибке.
Однако, существует один класс операций, где одна и та же операция ведёт себя по-разному с различными типами: расширенные операторы присваивания. Например, += изменяет списки, но не кортежи или числа (a_list += [1, 2, 3] эквивалентно a_list.extend([1, 2, 3])) и изменяет список, в то время, как some_tuple += (1, 2, 3) и some_int += 1 создают новый объект.
Если вы хотите знать, ссылаются ли 2 переменные на один объект или нет, вы можете использовать оператор is, или встроенную функцию id.
Как создавать функции более высокого порядка?
Есть два пути: использовать вложенные функции или вызываемые объекты. Например, с использованием вложенных функций:
def linear(a, b): def result(x): return a * x + b return result
Использование вызываемого объекта:
class linear: def __init__(self, a, b): self.a, self.b = a, b def __call__(self, x): return self.a * x + self.b
В обоих случаях,
taxes = linear(0.3, 2)
даёт функцию, что (к примеру) taxes(10e6) == 0.3 * 10e6 + 2.
Использование вызываемого объекта — немного медленнее, и в результате получается больше кода. Однако, заметьте, что несколько функций могут разделять свою сигнатуру с помощью наследования:
class exponential(linear): # __init__ наследуется def __call__(self, x): return self.a * (x ** self.b)
Объект может сохранять свое состояние для нескольких вызовов:
class counter: value = 0 def set(self, x): self.value = x def up(self): self.value = self.value + 1 def down(self): self.value = self.value - 1 count = counter() inc, dec, reset = count.up, count.down, count.set
Здесь inc, dec, reset выступают в роли функций, которые разделяют одну и ту же переменную.
Как скопировать объект в Python?
В общем случае, с помощью модуля copy.
Некоторые объекты можно скопировать более просто. Словари имеют метод copy:
newdict = olddict.copy()
Последовательности могут быть скопированы путём срезов:
new_l = l[:]
Как узнать доступные методы и атрибуты объекта?
dir(x) возвращает список методов и атрибутов.
Как можно узнать имя объекта?
Вообще говоря, никак, поскольку объекты в действительности не имеют имён. Важно: присваивание всегда связывает имя с объектом. Это верно и для инструкций def и class.
>>> class A: ... pass ... >>> B = A >>> >>> a = B() >>> b = a >>> print(b) <__main__.A object at 0x7fbcc3ee5160> >>> print(a) <__main__.A object at 0x7fbcc3ee5160>
Возможно, класс имеет имя: однако, хотя он связан с двумя именами и запрашивается через имя B, созданный экземпляр всё ещё считается экземпляром класса A. Однако, невозможно сказать, имя экземпляра a или b, поскольку оба они связаны с одним и тем же значением.
Какой приоритет у оператора «запятая»?
Запятая не является оператором в Python.
>>> "a" in "b", "a" (False, 'a')
Поскольку запятая — не оператор, но разделитель между выражениями, пример выше исполняется как если бы было введено:
("a" in "b"), "a"
А не
"a" in ("b", "a")
То же самое верно и для операторов присваивания (=, += и другие). Они не являются операторами как таковыми, а лишь синтаксическими разделителями в операциях присваивания.
Есть ли в Python эквивалент тернарного оператора «?:» в C?
Да. Синтаксис:
[on_true] if [expression] else [on_false]
x, y = 50, 25 small = x if x < y else y
Можно ли писать обфусцированные однострочники?
Можно.
from functools import reduce # Простые числа < 1000 print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000))))) # Первые 10 чисел Фибоначчи print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f), range(10)))) # Множество Мандельброта print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y, Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM, Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro, i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr( 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)) # ___ ___/ ___ ___/ | | |__ lines on screen # V V | |______ columns on screen # | | |__________ maximum of "iterations" # | |_________________ range on y axis # |____________________________ range on x axis
Не пытайтесь это делать дома!
Почему -22 // 10 равно -3?
Поскольку i % j имеет тот же знак, что j. А ещё
i == (i // j) * j + (i % j)
Как можно изменить строку?
Никак, поскольку строки неизменяемы. В большинстве ситуаций, нужно просто сделать новую строку из различных частей. Однако, если так нужно, можно использовать io.StringIO, либо модуль array:
>>> import io >>> s = "Hello, world" >>> sio = io.StringIO(s) >>> sio.getvalue() 'Hello, world' >>> sio.seek(7) 7 >>> sio.write("there!") 6 >>> sio.getvalue() 'Hello, there!' >>> import array >>> a = array.array('u', s) >>> print(a) array('u', 'Hello, world') >>> a[0] = 'y' >>> print(a) array('u', 'yello, world') >>> a.tounicode() 'yello, world'
Как использовать строки для вызова функций/методов?
Существует несколько приёмов.
- Лучший — использование словаря, ставящего соответствие строке функцию. Его главное достоинство — строки не обязаны совпадать с названиями функций.
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs dispatch[get_input()]()
- Использование встроенной функции getattr:
import foo getattr(foo, 'bar')()
- Использование locals или eval (не рекомендуется)
def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f() f = eval(fname) f()
Как удалить все символы новой строки в конце строки?
Можно использовать S.rstrip(«rn») для удаления символов новой строки, без удаления конечных пробелов:
>>> lines = ("line 1 rn" ... "rn" ... "rn") >>> lines.rstrip("rn") 'line 1 '
Как создать многомерный список?
Возможно, вы попробуете этот неудачный вариант:
>>> A = [[None] * 2] * 3
Это выглядит правильно, если напечатать:
>>> A [[None, None], [None, None], [None, None]]
Но если вы присвоите значение, то оно появится в нескольких местах:
>>> A[0][0] = 5 >>> A [[5, None], [5, None], [5, None]]
Причина в том, что оператор * не создаёт копию, а только ссылку на существующий объект. *3 создаёт список из 3 ссылок на один и тот же список. Изменение в одной строке изменяют другие, что, вероятно, не то, что вы хотите.
Возможные пути решения:
A = [None] * 3 for i in range(3): A[i] = [None] * 2
w, h = 2, 3 A = [[None] * w for i in range(h)]
Или, можно использовать специальные модули, предоставляющие матрицы. Наиболее известным является NumPy.
Почему a_tuple[i] += [‘item’] не работает, а добавление работает?
Это из-за того, что расширенный оператор присваивания — оператор присваивания, а также из-за разницы между изменяемыми и неизменяемыми объектами в Python.
Это обсуждение относится в общем, когда расширенные операторы присваивания применяются к элементам кортежа, которые указывают на изменяемые объекты, но мы будем использовать список и +=, как образец.
Если вы напишете:
>>> a_tuple = (1, 2) >>> a_tuple[0] += 1 Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Причина исключения должна быть понятна: 1 добавляется к объекту a_tuple[0], но когда мы пытаемся присвоить результат, 2, к первому элементу в кортеже, мы получаем ошибку, поскольку мы не можем изменить элемент кортежа.
То есть, это выражение делает следующее:
>>> result = a_tuple[0] + 1 >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Когда мы пишем что-то вроде:
>>> a_tuple = (['foo'], 'bar') >>> a_tuple[0] += ['item'] Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Исключение немного более неожиданное, но более удивителен тот факт, что, несмотря на ошибку, элемент добавился!
>>> a_tuple[0] ['foo', 'item']
Чтобы понять, что случилось, нужно знать, что:
- Если объект определяет метод __iadd__, он вызывается, когда выполняется +=, и возвращенное значение используется для присваивания
- Для списков, __iadd__ эквивалентен вызову extend для списка
Таким образом,
>>> a_list = [] >>> a_list += [1] >>> a_list [1]
Эквивалентен:
>>> result = a_list.__iadd__([1]) >>> a_list = result
Таким образом, наш пример с кортежом эквивалентен:
>>> result = a_tuple[0].__iadd__(['item']) >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
__iadd__ завершился успешно, и список увеличился, но присваивание законилось ошибкой.
Время прочтения
3 мин
Просмотры 19K
В языках программирования меня всегда интересовало их внутреннее устройство. Как работает тот или иной оператор? Почему лучше писать так, а не иначе? Подобные вопросы не всегда помогают решить задачу «здесь и сейчас», но в долгосрочной перспективе формируют общую картину языка программирования. Сегодня я хочу поделиться результатом одного из таких погружений и ответить на вопрос, что происходит при модификации 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 Лучано Ромальо. Очень рекомендуют ее почитать всем заинтересованным