Почему splatting создает кортеж справа, а список слева?

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

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

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

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

Почему?

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


person Paul Panzer    schedule 21.05.2019    source источник
comment
Тоже актуальный, хоть и не дурак. ссылка   -  person Paritosh Singh    schedule 21.05.2019


Ответы (7)


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

*whatever,

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

[*whatever]

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

{*whatever}

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


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

*target, = whatever

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

[*target] = whatever

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

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

first, *rest = iterable

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

person user2357112 supports Monica    schedule 22.05.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]

а здесь, где elements это кортеж

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

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

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

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

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

person Devesh Kumar Singh    schedule 21.05.2019
comment
Хотя PEP указывает причину, на самом деле она ее не дает. - person Paul Panzer; 21.05.2019
comment
Да, я сам пытаюсь найти причину @PaulPanzer :) - person Devesh Kumar Singh; 21.05.2019
comment
PEP 3132 говорит, что *elements, = range(6) создаст список слева- сторона руки. PEP 448 говорит, что elements = *range(6), обобщил распаковку в отображении кортежа, (*range(6),) с неявными скобками, создав кортеж с правой стороны. (копия @PaulPanzer) - person Andras Deak; 21.05.2019

В конце PEP 3132 -- Extended Iterable указано, почему Распаковка:

Принятие

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

[...]

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

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

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

person Thierry Lathuille    schedule 21.05.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 на отдельные значения, где общее количество значений может быть меньше, чем количество элементов в итерируемом объекте: одно из новых значений будет списком. всех оставшихся предметов.

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

(выделено мной). в то время как BUILD_TUPLE_UNPACK возвращает tuple:

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

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

person hiro protagonist    schedule 21.05.2019
comment
Так это техническая причина или нет? - person Paul Panzer; 21.05.2019
comment
нет, я не вижу здесь аргумента по какой-то причине... во втором случае может быть полезно иметь что-то изменяемое... но это всего лишь предположение. - person hiro protagonist; 21.05.2019

Для RHS особых проблем нет. ответ здесь говорит об этом хорошо :

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

elements = *iterable

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

elements = 1, 2, 3, 4,

это еще один способ инициализации кортежа.

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

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

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

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

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


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

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

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


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

1.

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

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

Стив

2.

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

Георг

3.

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

4.

При работе с итератором длина заранее неизвестна, поэтому единственный способ получить кортеж — сначала создать список, а затем создать из него кортеж. /strong> Грег

5.

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

Стив

пара совещаний пропущена

6.

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

-- Грег

пропущенные встречи

7.

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

  • списки возвращают списки
  • кортежи возвращают кортежи
  • Контейнеры XYZ возвращают контейнеры XYZ
  • итерации, не являющиеся контейнерами, возвращают итераторы.

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

-- Грег

8.

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

-- --Гвидо ван Россум

person Paritosh Singh    schedule 21.05.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 — это выражение для создания одного tuple из всех имен, а LHS — это привязка к нескольким именам из одного > tuple. Даже если вы видите LHS как кортеж имен, это не ограничивает тип каждого имени.


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

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

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

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

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

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

person MisterMiyagi    schedule 13.06.2019
comment
Для согласованности RHS и LHS всегда относятся к одному и тому же типу, т. е. кортеж и список. - какие? О чем ты говоришь? - person user2357112 supports Monica; 14.06.2019
comment
Кроме того, вы можете подумать, что способ работы RHS упрощает создание кортежа, но на самом деле Python фактически сначала создает список, а затем кортеж из списка. Попытка создать кортеж напрямую вызовет сложности, если размер результата нельзя определить заранее. - person user2357112 supports Monica; 14.06.2019
comment
@user2357112 user2357112 Я внес некоторые обширные изменения, чтобы удалить некоторые отвлекающие маневры, которые непреднамеренно подразумевали совпадающую мотивацию как для RHS, так и для LHS. Дело в том, что LHS проще использовать list. RHS должен быть tuple, точно так же, как {1, 2} должен быть set. Есть ли под ним некоторое преобразование list->tuple, это не меняет. - person MisterMiyagi; 14.06.2019

Использование a = *b,:

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

a = *[1, 2, 3],

Это даст:

(1, 2, 3)

Потому что:

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

[*[1, 2, 3]]

Выход:

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

  1. Распаковка дает три элемента, а для [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)

To be:

'ello'

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

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

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

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

Резюме:

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

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

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

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

print([*a])

Или кортеж:

print((*a))

И набор:

print({*a})

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

person U11-Forward    schedule 26.06.2019