Давайте посмотрим на лес сначала, прежде чем смотреть на деревья.
Здесь есть много информативных ответов с большими подробностями, я не буду повторять ни одного из них. Ключ к программированию в JavaScript имеет сначала правильную ментальную модель общего исполнения.
Хорошие новости заключается в том, что, если вы хорошо понимаете этот момент, вам никогда не придется беспокоиться о гоночных условиях. Прежде всего вы должны понимать, как вы хотите упорядочить свой код как по существу ответ на разные дискретные события, и как вы хотите объединить их в логическую последовательность. Вы можете использовать обещания или новые асинхронные / ожидающие более высокие уровни в качестве инструментов для этой цели, или вы можете откатывать свои собственные.
Но вы не должны использовать какие-либо тактические инструменты для решения проблемы, пока вам не понравится актуальная проблемная область. Нарисуйте карту этих зависимостей, чтобы знать, что нужно запускать, когда. Попытка ad-hoc подхода ко всем этим обратным вызовам просто не поможет вам.
Вы всегда можете изменить изменяемое значение внутри кортежа. Непонятное поведение, которое вы видите с помощью
>>> thing[0] += 'd'
, вызвано +=
. Оператор +=
выполняет добавление на месте, но также присваивает - дополнение на месте работает только с файлом, но присваивание не выполняется, поскольку кортеж является неизменным. Думать об этом, как
>>> thing[0] = thing[0] + 'd'
, объясняет это лучше. Мы можем использовать модуль dis
из стандартной библиотеки, чтобы посмотреть на байт-код, сгенерированный из обоих выражений. С +=
мы получаем байт-код INPLACE_ADD
:
>>> def f(some_list):
... some_list += ["foo"]
...
>>> dis.dis(f)
2 0 LOAD_FAST 0 (some_list)
3 LOAD_CONST 1 ('foo')
6 BUILD_LIST 1
9 INPLACE_ADD
10 STORE_FAST 0 (some_list)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
С +
получаем a BINARY_ADD
:
>>> def g(some_list):
... some_list = some_list + ["foo"]
>>> dis.dis(g)
2 0 LOAD_FAST 0 (some_list)
3 LOAD_CONST 1 ('foo')
6 BUILD_LIST 1
9 BINARY_ADD
10 STORE_FAST 0 (some_list)
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
Обратите внимание, что мы получаем STORE_FAST
в обоих местах. Это байт-код, который терпит неудачу, когда вы пытаетесь сохранить обратно в кортеж - INPLACE_ADD
, который появляется перед началом работы.
Это объясняет, почему случай «Не работает и работает» оставляет измененный список позади: кортеж уже имеет ссылку на список:
>>> id(thing[0])
3074072428L
Список затем изменен с помощью INPLACE_ADD
и STORE_FAST
:
>>> thing[0] += 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Таким образом, кортеж все еще имеет ссылку на тот же список, но список был изменен в -place:
>>> id(thing[0])
3074072428L
>>> thing[0]
['b', 'c', 'd']
Вы не можете изменять кортеж, но вы можете изменить содержимое вещей, содержащихся в кортеже. Списки (вместе с наборами, dicts и объектами) являются ссылочным типом , и, таким образом, «вещь» в кортеже является просто ссылкой - фактический список является изменяемым объектом, на который указывает эта ссылка и может быть изменен без изменения самой ссылки.
( + ,) <--- your tuple (this can't be changed)
|
|
v
['a'] <--- the list object your tuple references (this can be changed)
После thing[0][0] = 'b'
:
( + ,) <--- notice how the contents of this are still the same
|
|
v
['b'] <--- but the contents of this have changed
После thing[0].append('c')
:
( + ,) <--- notice how this is still the same
|
|
v
['b','c'] <--- but this has changed again
Причина, по которой ошибки +=
заключаются в том, что она не полностью эквивалентна .append()
- на самом деле это добавление, а затем назначение (и присваивание не выполняется), а не просто добавление на место .
Вы не можете заменить элемент кортежа, но вы можете заменить все содержимое элемента. Это будет работать:
thing[0][:] = ['b']