Понимание * x, = lst

Я просматриваю старый код, пытаясь понять, что он делает, и я наткнулся на это нечетное утверждение:

*x ,= p

p - это список в этом контексте. Я пытался выяснить, что делает это утверждение. Насколько я могу судить, он просто устанавливает x значение p. Например:

p = [1,2]
*x ,= p    
print(x)

Просто дает

[1, 2]

Разве это не отличается от x = p? Любая идея, что делает этот синтаксис?

Ответ 1

*x ,= p в основном является запутанной версией x = list(p), используя расширенную итеративную распаковку. Для запятой после x требуется, чтобы назначение предназначалось для кортежа (он также может быть списком).

*x, = p отличается от x = p, потому что первая создает копию p (т.е. новый список), а вторая создает ссылку на исходный список. Чтобы проиллюстрировать:

>>> p = [1, 2]
>>> *x, = p 
>>> x == p
True
>>> x is p
False
>>> x = p
>>> x == p
True
>>> x is p
True

Ответ 2

Это функция, которая была введена в Python 3.0 (PEP 3132). В Python 2 вы можете сделать что-то вроде этого:

>>> p = [1, 2, 3]
>>> q, r, s = p
>>> q
1
>>> r
2
>>> s
3

Python 3 расширил это, чтобы одна переменная могла содержать несколько значений:

>>> p = [1, 2, 3]
>>> q, *r = p
>>> q
1
>>> r
[2, 3]

Таким образом, это то, что используется здесь. Однако вместо двух переменных, чтобы удерживать три значения, это всего лишь одна переменная, которая принимает каждое значение в списке. Это отличается от x = p, потому что x = p означает, что x является другим именем для p. В этом случае, однако, это новый список, который просто имеет одинаковые значения в нем. (Возможно, вас заинтересует "Наименьшее изумление" и параметр Mutable Default Argument)

Два других распространенных способа создания этого эффекта:

>>> x = list(p)

и

>>> x = p[:]

Начиная с Python 3.3, у объекта списка фактически есть метод, предназначенный для копирования:

x = p.copy()

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

Ответ 3

Вы всегда должны бросать их в dis и посмотреть, что он отбрасывает вам; вы увидите, как *x, = p действительно отличается от x = p:

dis('*x, = p')
  1           0 LOAD_NAME                0 (p)
              2 UNPACK_EX                0
              4 STORE_NAME               1 (x)

В то время как простой оператор присваивания:

dis('x = p')
  1           0 LOAD_NAME                0 (p)
              2 STORE_NAME               1 (x)

(Отключение несвязанных None возвращает)

Как вы можете видеть, UNPACK_EX - это другой op-код между ними; он задокументирован как:

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

Вот почему, как отметил Юджин, вы получаете новый объект, на который ссылается имя x, а не ссылка на уже существующий объект (как в случае с x = p).


*x, выглядит очень странно (дополнительная запятая там и все), но она требуется здесь. Левая сторона должна быть либо кортежем, либо списком, и из-за причудливости создания одного элемента кортежа в Python вам нужно использовать трейлинг ,:

i = 1, # one element tuple

Если вам нравятся запутывающие люди, вы всегда можете использовать версию list:

[*x] = p

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