Почему присвоение пустому списку (например, [] = «») не является ошибкой?

110

В python 3.4 я набираю

[] = "" 

и он работает нормально, никаких исключений не возникает. Хотя потом конечно []не равняется "".

[] = ()

тоже отлично работает.

"" = []

вызывает исключение, как ожидалось,

() = ""

вызывает исключение, как и ожидалось. Так что же происходит?

Вардд
источник

Ответы:

132

Вы не сравниваете равенство. Вы назначаете .

Python позволяет назначать несколько целей:

foo, bar = 1, 2

присваивает два значения fooи barсоответственно. Все, что вам нужно, - это последовательность или итерация с правой стороны и список или кортеж имен слева.

Когда вы это сделаете:

[] = ""

вы назначили пустую последовательность (пустые строки - это все еще последовательности) пустому списку имен.

По сути, это то же самое, что делать:

[foo, bar, baz] = "abc"

где вы получите foo = "a", bar = "b"и baz = "c", но с меньшим количеством символов.

Однако вы не можете назначить строку, поэтому ""левая часть назначения никогда не работает и всегда является синтаксической ошибкой.

См. Документацию по операторам присвоения :

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

и

Присвоение объекта целевому списку, необязательно заключенному в круглые или квадратные скобки , рекурсивно определяется следующим образом.

Акцент мой .

То, что Python не выдает синтаксическую ошибку для пустого списка, на самом деле является небольшой ошибкой! Официально задокументированная грамматика не позволяет использовать пустой целевой список, а для пустого ()вы получите ошибку. См. Ошибку 23275 ; считается безобидным багом:

Отправной точкой является признание того, что это существует уже очень давно и безвредно.

Также см. Почему допустимо присвоение пустому списку, но не пустому кортежу?

Мартейн Питерс
источник
36

Это следует правилам раздела инструкций присваивания из документации,

assignment_stmt ::=  (target_list "=")+ (expression_list | yield_expression)

Если target listэто список целей, разделенных запятыми: объект должен быть итерируемым с тем же количеством элементов, что и целевые объекты в целевом списке, и элементы назначаются слева направо соответствующим целевым объектам.

Объект должен быть последовательностью с тем же количеством элементов, что и целевые объекты в целевом списке, и элементы назначаются слева направо соответствующим целевым объектам.

Итак, когда вы говорите

[] = ""

"" является итерируемым (любая допустимая строка python является итерируемым), и он распаковывается по элементам списка.

Например,

>>> [a, b, c] = "123"
>>> a, b, c
('1', '2', '3')

Поскольку у вас пустая строка и пустой список, распаковывать нечего. Итак, ошибки нет.

Но попробуйте это

>>> [] = "1"
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: too many values to unpack (expected 0)
>>> [a] = ""
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: need more than 0 values to unpack

В этом [] = "1"случае вы пытаетесь распаковать строку "1"по пустому списку переменных. Таким образом, он жалуется на «слишком много значений для распаковки (ожидается 0)».

Таким же образом, в [a] = ""случае, если у вас есть пустая строка, поэтому на самом деле нечего распаковывать, но вы распаковываете ее по одной переменной, что, опять же, невозможно. Вот почему он жалуется, что «нужно более 0 значений для распаковки».

Кроме того, как вы заметили,

>>> [] = ()

также не выдает ошибки, потому что ()это пустой кортеж.

>>> ()
()
>>> type(())
<class 'tuple'>

а когда распаковывается по пустому списку, распаковывать нечего. Так что никакой ошибки.


Но когда вы это сделаете

>>> "" = []
  File "<input>", line 1
SyntaxError: can't assign to literal
>>> "" = ()
  File "<input>", line 1
SyntaxError: can't assign to literal

как говорится в сообщении об ошибке, вы пытаетесь назначить строковый литерал. Что невозможно. Вот почему вы получаете ошибки. Это как сказать

>>> 1 = "one"
  File "<input>", line 1
SyntaxError: can't assign to literal

Внутренности

Внутри эта операция присваивания будет преобразована в UNPACK_SEQUENCEкод операции,

>>> dis(compile('[] = ""', "string", "exec"))
  1           0 LOAD_CONST               0 ('')
              3 UNPACK_SEQUENCE          0
              6 LOAD_CONST               1 (None)

Здесь, поскольку строка пуста, UNPACK_SEQUENCEраспаковывается 0раз. Но когда у вас есть что-то вроде этого

>>> dis(compile('[a, b, c] = "123"', "string", "exec"))
  1           0 LOAD_CONST               0 ('123')
              3 UNPACK_SEQUENCE          3
              6 STORE_NAME               0 (a)
              9 STORE_NAME               1 (b)
             12 STORE_NAME               2 (c)
             15 LOAD_CONST               1 (None)
             18 RETURN_VALUE

последовательность 123распаковывается в стек справа налево. Итак, верхняя часть стека будет, 1следующая будет, 2и последняя будет 3. Затем он назначает переменные из левого выражения одну за другой сверху стека.


Кстати, в Python вы можете выполнять несколько назначений в одном выражении. Например,

a, b, c, d, e, f = u, v, w, x, y, z

это работает, потому что правые значения используются для построения кортежа, а затем он будет распакован по левым значениям.

>>> dis(compile('a, b, c, d, e, f = u, v, w, x, y, z', "string", "exec"))
  1           0 LOAD_NAME                0 (u)
              3 LOAD_NAME                1 (v)
              6 LOAD_NAME                2 (w)
              9 LOAD_NAME                3 (x)
             12 LOAD_NAME                4 (y)
             15 LOAD_NAME                5 (z)
             18 BUILD_TUPLE              6
             21 UNPACK_SEQUENCE          6
             24 STORE_NAME               6 (a)
             27 STORE_NAME               7 (b)
             30 STORE_NAME               8 (c)
             33 STORE_NAME               9 (d)
             36 STORE_NAME              10 (e)
             39 STORE_NAME              11 (f)
             42 LOAD_CONST               0 (None)
             45 RETURN_VALUE

но классическая техника обмена a, b = b, aиспользует вращение элементов в верхней части стека. Если у вас есть только два или три элемента , то они обрабатывают специальными ROT_TWOи ROT_THREEинструкции вместо построения кортежа и распаковки.

>>> dis(compile('a, b = b, a', "string", "exec"))
  1           0 LOAD_NAME                0 (b)
              3 LOAD_NAME                1 (a)
              6 ROT_TWO
              7 STORE_NAME               1 (a)
             10 STORE_NAME               0 (b)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
thefourtheye
источник
Так же можно пользоваться dis('[] = ""')без звонка compile().
Андреа Корбеллини
Можете ли вы описать, что произойдет, если вы меняете местами более трех переменных / элементов, используя метод из вашего последнего примера?
нанофарад
@hexafraction Он построит новый кортеж со всеми элементами с правой стороны, а затем распакует их по переменным с левой стороны.
thefourtheye
@hexafraction: см. Как происходит
Мартейн Питерс