Почему splatting создает кортеж на rhs, а список на lhs? - PullRequest
69 голосов
/ 21 мая 2019

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

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.

Почему?

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

Ответы [ 7 ]

45 голосов
/ 22 мая 2019

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

*whatever,

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

[*whatever]

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

{*whatever}

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


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

*target, = whatever

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

[*target] = whatever

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

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

first, *rest = iterable

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

29 голосов
/ 21 мая 2019

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

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

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

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

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

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

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

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 ниже в оценке, поэтому создание списка, изменяемого значения, а не кортежа, имеет больше смысла

18 голосов
/ 21 мая 2019

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

Принятие

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

[...]

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

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

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

13 голосов
/ 21 мая 2019

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

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:

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

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

9 голосов
/ 21 мая 2019

Для 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 фиксируется операцией, так что вы можете рассчитывать на его поведение.

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


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

1

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

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

Стив

2

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

Георг

3.

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

4.

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

5.

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

STeVe

пара конвоев пропущено

6.

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

- Greg

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

7.

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

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

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

- Грег

8.

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

- - Guido van Rossum

4 голосов
/ 26 июня 2019

Использование 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, есть причина, в "Основной части:"section.

Резюме:

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

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

(выделено мной)

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

print([*a])

Или кортеж:

print((*a))

И набор:

print({*a})

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

4 голосов
/ 13 июня 2019

TLDR: вы получаете 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 - это выражение для создания one tuple из всех имен - LHS является привязкой к нескольким именам из one tuple. Даже если вы видите LHS как набор имен, это не ограничивает тип каждого имени.


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

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

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

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

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

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

...