Почему splatting создает кортеж на rhs, а список на lhs?

Рассмотрим, например,

squares = *map((2).__rpow__, range(5)),
squares
# (0, 1, 4, 9, 16)

*squares, = map((2).__rpow__, range(5))
squares
# [0, 1, 4, 9, 16]

Итак, при прочих равных мы получаем список при разбивке по lhs и кортеж при разбивке по rhs.

Зачем?

Это по замыслу, и если да, то каково обоснование? Или, если нет, есть ли какие-либо технические причины? Или это просто так, без особой причины?

Ответ 1

Тот факт, что вы получаете кортеж на RHS, не имеет ничего общего со сплатом. Сплат просто распаковывает ваш итератор map. То, во что вы его распаковываете, определяется тем, что вы использовали синтаксис кортежа:

*whatever,

вместо синтаксиса списка:

[*whatever]

или установите синтаксис:

{*whatever}

Вы могли бы получить список или набор. Вы только что сказали Python сделать кортеж.


На LHS разделенная цель назначения всегда создает список. Неважно, используете ли вы "стиль кортежа"

*target, = whatever

или "стиль списка"

[*target] = whatever

синтаксис для списка целей. Синтаксис очень похож на синтаксис для создания списка или кортежа, но синтаксис целевого списка - это совсем другое.

Синтаксис, который вы используете слева, был введен в PEP 3132 для поддержки вариантов использования, таких как

first, *rest = iterable

В назначении для распаковки элементы итерируемого объекта назначаются для не помеченных звездочками целей по позициям, и, если есть помеченная звездочкой цель, любые дополнительные элементы вставляются в список и назначаются этой цели. Список был выбран вместо кортежа, чтобы облегчить дальнейшую обработку. Поскольку в вашем примере у вас есть только помеченная цель, все элементы попадают в список "дополнительные", назначенные этой цели.

Ответ 2

Это указано в недостатках PEP-0448

В то время как *elements, = iterable заставляет элементы быть списком, elements = *iterable, заставляет элементы быть кортежем. Причина этого может смущать людей, незнакомых с конструктом.

Также согласно спецификации PEP-3132

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

Также упоминается здесь: Python-3 exprlists

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

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

In [27]: *elements, = range(6)                                                                                                                                                      

In [28]: elements                                                                                                                                                                   
Out[28]: [0, 1, 2, 3, 4, 5]

и здесь, где элементы является кортеж

In [13]: elements = *range(6),                                                                                                                                                      

In [14]: elements                                                                                                                                                                   
Out[14]: (0, 1, 2, 3, 4, 5)

Из того, что я мог понять из комментариев и других ответов:

  • Первое, что нужно сделать, это сохранить существующие списки произвольных аргументов, используемые в функциях, т.е. *args

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

Ответ 3

Существует указание причины, по которой в конце PEP 3132 - расширенная повторяемая распаковка:

принятие

После короткого обсуждения списка python-3000 [1], PEP был принят Гвидо в его нынешнем виде. Обсуждались возможные изменения:

[...]

Сделайте помеченную цель кортежем вместо списка. Это будет соответствовать функции * args, но усложнит дальнейшую обработку результата.

[1] https://mail.python.org/pipermail/python-3000/2007-May/007198.html

Таким образом, преимущество наличия изменяемого списка вместо неизменяемого кортежа, кажется, является причиной.

Ответ 4

не полный ответ, но разборка дает некоторые подсказки:

from dis import dis

def a():
    squares = (*map((2).__rpow__, range(5)),)
    # print(squares)

print(dis(a))

разбирает как

  5           0 LOAD_GLOBAL              0 (map)
              2 LOAD_CONST               1 (2)
              4 LOAD_ATTR                1 (__rpow__)
              6 LOAD_GLOBAL              2 (range)
              8 LOAD_CONST               2 (5)
             10 CALL_FUNCTION            1
             12 CALL_FUNCTION            2
             14 BUILD_TUPLE_UNPACK       1
             16 STORE_FAST               0 (squares)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

в то время как

def b():
    *squares, = map((2).__rpow__, range(5))
print(dis(b))

результаты в

 11           0 LOAD_GLOBAL              0 (map)
              2 LOAD_CONST               1 (2)
              4 LOAD_ATTR                1 (__rpow__)
              6 LOAD_GLOBAL              2 (range)
              8 LOAD_CONST               2 (5)
             10 CALL_FUNCTION            1
             12 CALL_FUNCTION            2
             14 UNPACK_EX                0
             16 STORE_FAST               0 (squares)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

документ по UNPACK_EX гласит:

UNPACK_EX (отсчеты)

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

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

(акцент мой). пока BUILD_TUPLE_UNPACK возвращает tuple:

BUILD_TUPLE_UNPACK (количество)

Извлекает число стеков из стека, объединяет их в один кортеж и выводит результат. Реализует итеративную распаковку в дисплеях кортежей (* x, * y, * z).

Ответ 5

Для RHS не так много проблем. ответ здесь гласит:

У нас это работает как обычно при вызовах функций. Он расширяет содержимое итерируемого объекта, к которому он прикреплен. Итак, утверждение:

elements = *iterable

можно рассматривать как:

elements = 1, 2, 3, 4,

что является еще одним способом инициализации кортежа.

Теперь для LHS, Да, есть технические причины, по которым LHS использует список, как указано в обсуждении первоначального PEP 3132 для расширения распаковки.

Причины можно почерпнуть из разговора о ПКП (добавлено в конце).

По сути, это сводится к паре ключевых факторов:

  • LHS должен был поддерживать "помеченное звездой выражение", которое не обязательно ограничивалось только концом.
  • RHS должен был позволять принимать различные типы последовательностей, включая итераторы.
  • Комбинация двух вышеперечисленных пунктов потребовала манипулирования/мутации содержимого после принятия их в помеченное звездой выражение.
  • Альтернативный подход к обработке, имитирующий итератор, питаемый RHS, даже оставляя в стороне трудности с реализацией, был сбит Гвидо за его непоследовательное поведение.
  • Учитывая все вышеперечисленные факторы, кортеж LHS должен быть сначала списком, а затем преобразован. Этот подход тогда просто добавит накладные расходы и не вызовет дальнейшего обсуждения.

Резюме: Сочетание различных факторов привело к решению о включении списка в LHS, а также причины, исходящие друг от друга.


Соответствующий экстракт для запрета несовместимых типов:

Важный вариант использования в Python для предложенной семантики - это когда у вас есть запись переменной длины, первые из которых интересны, а остальные менее важны, но не менее важны. (Если вы хотите выбросить все остальное, просто напишите a, b, c = x [: 3] вместо a, b, c, * d = x.) Для этого варианта использования гораздо удобнее, если тип d фиксируется операцией, поэтому вы можете рассчитывать на ее поведение.

Существует ошибка в дизайне filter() в Python 2 (которая будет исправлена в 3.0, если превратить его в итератор): если вход является кортежем, вывод тоже является кортежем, но если вход является списком или что-нибудь еще, выводом является список. Это абсолютно безумная подпись, поскольку это означает, что вы не можете рассчитывать на то, что результат является списком или кортежем - если вам нужно, чтобы он был одним или другим, вы должны преобразовать его в один, что это пустая трата времени и пространства. Пожалуйста, дайте не повторять эту ошибку дизайна. -Guido


Я также попытался воссоздать частично цитируемый разговор, который относится к изложенному выше. Источник Акцент мой.

1.

В списках аргументов * args исчерпывает итераторы, превращая их в кортежи. Я думаю, что было бы странно, если бы * args в распаковке кортежей не делал то же самое.

Это поднимает вопрос о том, почему патч создает списки, а не кортежи. Какая причина этого?

Стив

2.

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

Georg

3.

Ну, если это то, к чему вы стремитесь, то я бы ожидал, что было бы более полезно, чтобы распаковка генерировала не списки, а тот же тип, с которого вы начали, например, если я начал со строки, я, вероятно, хочу продолжить использовать строки :: --additional текст оторван

4.

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

5.

Ага. Это было одной из причин, по которой было предложено, чтобы * args появлялись только в конце распаковки кортежа.

Стив

пара конвоев пропустила

6.

Я не думаю, что возвращение указанного типа является целью, к которой следует стремиться, потому что она может работать только для фиксированного набора известных типов. Учитывая произвольный тип последовательности, нет способа узнать, как создать его новый экземпляр с указанным содержимым.

- Грег

пропущенные конвои

7.

Я предлагаю, чтобы:

  • списки возвращают списки
  • кортежи возвращают кортежи
  • Контейнеры XYZ возвращают контейнеры XYZ
  • Неконтейнерные итераторы возвращают итераторы.

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

- Грег

8.

Но я ожидаю меньше полезного. Он также не будет поддерживать "a, * b, c =". Из POV реализации, если у вас есть неизвестный объект в RHS, вы должны попытаться нарезать его, прежде чем пытаться повторить его; это может вызвать проблемы, например, если объект оказывается defaultdict - поскольку x [3:] реализован как x [slice (None, 3, None)], defaultdict даст вам значение по умолчанию. Я бы скорее определил это в терминах итерации по объекту до его исчерпания, который можно оптимизировать для определенных известных типов, таких как списки и кортежи.

- - -Guido ван Россум

Ответ 6

TL;DR: Вы получаете tuple на RHS, потому что вы попросили его. Вы получите list на LHS, потому что это проще.


Важно помнить, что RHS оценивается до LHS - вот почему a, b = b, a работает. Различие становится очевидным при разделении назначения и использовании дополнительных возможностей для LHS и RHS:

# RHS: Expression List
a = head, *tail
# LHS: Target List
*leading, last = a

Короче говоря, несмотря на то, что они выглядят одинаково, это совершенно разные вещи. RHS - это выражение для создания одного tuple из всех имен - LHS - это привязка к нескольким именам из одного tuple. Даже если вы видите LHS как кортеж имен, это не ограничивает тип каждого имени.


RHS - это список выражений - литерал tuple без дополнительных скобок (). Это аналогично тому, как 1, 2 создает кортеж даже без скобок, и как включение [] или {} создает list или set. *tail просто означает распаковку в этот tuple.

Новое в версии 3.5: повторяемая распаковка в списках выражений, первоначально предложенная PEP 448.

LHS не создает одно значение, оно связывает значения с несколькими именами. При использовании универсального имени, такого как *leading, привязка не известна заранее во всех случаях. Вместо этого все остальное содержит все, что осталось.

Использование list для хранения значений делает это проще - значения для конечных имен могут быть эффективно удалены с конца. Затем оставшийся list содержит в точности значения для универсального имени. Фактически, это именно то, что делает CPython:

  • собрать все предметы для обязательных целей, прежде чем пометить
  • собрать все оставшиеся элементы из повторяемого в списке
  • выбрасывать элементы для обязательных целей после отмеченной звездочкой из списка
  • поместите отдельные элементы и список измененных размеров в стек

Даже если LHS имеет универсальное имя без конечных имен, это list для согласованности.

Ответ 7

Используя a = *b, ,:

Если вы делаете:

a = *[1, 2, 3],

Это дало бы:

(1, 2, 3)

Так как:

  1. Распаковка и некоторые другие вещи дают кортежи по умолчанию, но если вы говорите, т.е.

    [*[1, 2, 3]]

    Выход:

    [1, 2, 3] как list так как я делаю list, поэтому {*[1, 2, 3]} даст set.

  2. Распаковка дает три элемента, а для [1, 2, 3] это действительно просто

    1, 2, 3

    Какие выводы:

    (1, 2, 3)

    Вот что делает распаковка.

Основная часть:

Распаковка просто выполняет:

1, 2, 3

За:

[1, 2, 3]

Который является кортежем:

(1, 2, 3)

На самом деле это создает список и превращает его в кортеж.

Используя *a, = b:

Ну, это действительно будет

a = [1, 2, 3]

Так как это не так:

*a, b = [1, 2, 3]

Или что-то подобное, в этом не так много.

  1. Это эквивалентно без * и , не полностью, он просто всегда дает список.

  2. Это на самом деле почти только используется для нескольких переменных, то есть:

    *a, b = [1, 2, 3]

Одна вещь заключается в том, что независимо от того, что он хранит тип списка:

>>> *a, = {1,2,3}
>>> a
[1, 2, 3]
>>> *a, = (1,2,3)
>>> a
[1, 2, 3]
>>> 

Также было бы странно иметь:

a, *b = 'hello'

А также:

print(b)

Быть:

'ello'

Тогда это не похоже на брызги.

Также list имеет больше функций, чем другие, проще в обращении.

Вероятно, нет причин для этого, это действительно решение в Python.

В разделе a = *b, есть причина, в разделе "Основная часть:".

Резюме:

Также как @Devesh упомянул здесь недостатки в PEP 0448:

В то время как * elements, = iterable заставляет элементы быть списком, elements = * iterable заставляет элементы быть кортежем. Причина этого может смущать людей, незнакомых с конструктом.

(акцент мой)

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

print([*a])

Или кортеж:

print((*a))

И набор:

print({*a})

И так далее...