изменяемый тип внутри неизменяемого контейнера - PullRequest
10 голосов
/ 07 февраля 2012

Я немного озадачен изменением членов кортежа. Следующее не работает:

>>> thing = (['a'],)
>>> thing[0] = ['b']
TypeError: 'tuple' object does not support item assignment
>>> thing
(['a'],)

Но это работает:

>>> thing[0][0] = 'b'
>>> thing
(['b'],)

Также работает:

>>> thing[0].append('c')
>>> thing
(['b', 'c'],)

Не работает, а работает (а? ?!):

>>> thing[0] += 'd'
TypeError: 'tuple' object does not support item assignment
>>> thing
(['b', 'c', 'd'],)

На первый взгляд эквивалентно предыдущему, но работает:

>>> e = thing[0]
>>> e += 'e'
>>> thing
(['b', 'c', 'd', 'e'],)

Так каковы правила игры, когда вы можете и не можете что-то изменить внутри кортежа? Похоже, это больше похоже на запрет использования оператора присваивания для членов кортежа, но последние два случая меня смущают.

Ответы [ 3 ]

11 голосов
/ 07 февраля 2012

Вы можете всегда изменять изменяемое значение внутри кортежа. Загадочное поведение, которое вы видите с

>>> 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        

С + мы получаем 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

Таким образом, кортеж все еще имеет ссылку на тот же список , но список был изменен на месте:

>>> id(thing[0])
3074072428L
>>> thing[0] 
['b', 'c', 'd']
3 голосов
/ 07 февраля 2012

Вы не можете изменить кортеж, но вы можете изменить содержимое вещей, содержащихся в кортеже.Списки (вместе с наборами, диктовками и объектами) являются ссылочным типом и, таким образом, "вещь" в кортеж является просто ссылкой - фактический список является изменяемым объектом, которыйуказывается этой ссылкой и может быть изменено без изменения самой ссылки.

( + ,)       <--- 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() - она ​​фактически выполняет добавление, а затем присваивание (и назначение не выполняется), а не просто добавление в-местная.

1 голос
/ 07 февраля 2012

Вы не можете заменить элемент кортежа, но вы можете заменить все содержимое элемента. Это будет работать:

thing[0][:] = ['b']
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...