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

В python 3.4, я печатаю

[] = "" 

и он отлично работает, не возникает Исключение. Хотя, конечно, [] после этого не равно "".

[] = ()

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

"" = []

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

() = ""

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

Ответ 1

Вы не сравниваете для равенства. Вы назначаете.

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

foo, bar = 1, 2

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

Когда вы выполните:

[] = ""

вам была присвоена пустая последовательность (пустые строки - это последовательности) - пустой список имен.

Это, по сути, то же самое, что и делать:

[foo, bar, baz] = "abc"

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

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

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

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

и

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

Акцент на мой.

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

Исходная точка - это признание того, что это было очень долго и безвредно.

Также см. Почему это допустимо для назначения пустого списка, но не для пустого кортежа?

Ответ 2

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

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 op code,

>>> 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. Затем он присваивает вершине стека переменные из выражения левой стороны один за другим.


BTW, в 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