Распаковывание обобщений

>>> LOL = [[1, 2], ['three']]
>>> [*LOL[0], *LOL[1]]
[1, 2, 'three']

Хорошо! До свидания itertools.chain. Так или иначе, вам никогда не нравилось.

>>> [*L for L in LOL]
  File "<ipython-input-21-e86d2c09c33f>", line 1
    [*L for L in LOL]
    ^
SyntaxError: iterable unpacking cannot be used in comprehension

О. Почему у нас нет хороших вещей?

Распаковка в понимании кажется очевидной /pythonic, но поскольку они потрудились добавить это специальное сообщение об ошибке, была причина его отключения. Итак, что проблема с этим синтаксисом?

Ответы

Ответ 1

Взяв цитату из поток списка рассылки Py-Dev, в котором эта функция была принята:

Итак, это оставляет понимание. IIRC, во время разработки патча мы поняли, что f(*x for x in xs) достаточно двусмысленный, который мы решили запретить, - обратите внимание, что f(x for x in xs) уже является некоторым частным случаем, потому что аргумент может быть только выражение "голого" генератора, если оно является единственным аргументом. То же рассуждение не применяется (в этой форме), чтобы перечислить, установить и определить выражения - в то время как f(x for x in xs) идентично по значению f((x for x in xs)), [x for x in xs] НЕ совпадает с [(x for x in xs)] (что список один элемент, а элемент является генератором выражение)

(Акцент мой)

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

В страхе от ссылки-гниения я прикрепляю сообщение (отформатированное) здесь:

Итак, я думаю, что тестовая функция здесь должна быть:

def f(*a, **k): print(list(a), list(k))

Затем мы можем попробовать такие вещи, как:

f(x for x in ['ab', 'cd'])

который печатает объект-генератор, потому что это интерпретируется как аргумент, который выражает генератор.

Но теперь рассмотрим:

f(*x for x in ['ab', 'cd'])

Я лично ожидал, что это будет эквивалентно:

f(*'ab', *'cd')

IOW:

 f('a', 'b', 'c', 'd')

PEP не дает ясности относительно того, что здесь делать. Вопрос в том, должны ли мы интерпретировать такие вещи, как *x for x in ... как расширенную форму выражения генератора, или как расширенную форму *arg? Я как-то думаю, что последнее более полезно, а также более логичное расширение.

Мое рассуждение состоит в том, что PEP поддерживает такие вещи, как f(*a, *b), и было бы довольно логично интерпретировать f(*x for x in xs) как выполнение объекта *x для каждого x в списке xs.

Наконец, как отмечено в разделе Аннотация соответствующего PEP, эта функция не полностью исключена:

Этот PEP не включает распаковку операторов внутри списка, набора и понимания словаря, хотя это не исключалось для будущих предложений.

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

Ответ 2

Это кратко объясняется в PEP 448, который вводит распаковки обобщений:

Ранее итерации этого PEP допускали распаковку операторов внутри списка, набора и словаря в качестве оператора сглаживания над итерации контейнеров:

>>> ranges = [range(i) for i in range(5)]
>>> [*item for item in ranges]
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]

>>> {*item for item in ranges}
{0, 1, 2, 3}

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

Однако это может измениться в будущем:

Этот PEP не включает распаковку операторов внутри списка, набора и понимания словаря, хотя это не исключено для будущих предложений.