Почему 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, 2, 3]]
Выход:
[1, 2, 3]
как list
так как я делаю list
, поэтому {*[1, 2, 3]}
даст set
.
-
Распаковка дает три элемента, а для [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]
Или что-то подобное, в этом не так много.
-
Это эквивалентно без *
и ,
не полностью, он просто всегда дает список.
-
Это на самом деле почти только используется для нескольких переменных, то есть:
*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})
И так далее...