Понимание понимания списков для выравнивания списка списков в Python

Я нашел это понимание, которое идеально подходит для выравнивания списка списков:

>>> list_of_lists = [(1,2,3),(2,3,4),(3,4,5)]
>>> [item for sublist in list_of_lists for item in sublist]
[1, 2, 3, 2, 3, 4, 3, 4, 5]

Мне нравится это лучше чем использование itertools.chain(), но я просто не могу этого понять. Я пробовал окружать части скобками, чтобы посмотреть, смогу ли я уменьшить сложность, но теперь я просто запутался:

>>> [(item for sublist in list_of_lists) for item in sublist]
[<generator object <genexpr> at 0x7ff919fdfd20>, <generator object <genexpr> at 0x7ff919fdfd70>, <generator object <genexpr> at 0x7ff919fdfdc0>]

>>> [item for sublist in (list_of_lists for item in sublist)]
[5, 5, 5]

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

Кто-нибудь может объяснить, что именно здесь происходит?

3 ответа

Решение

Читайте циклы for, как если бы они были вложенными, слева направо. Выражение слева является тем, которое производит каждое значение в окончательном списке:

for sublist in list_of_lists:
    for item in sublist:
        item  # added to the list

Список понимания также поддерживает if тесты для фильтрации того, какие элементы используются; они также могут рассматриваться как вложенные операторы, так же, как for петли.

Добавляя скобки, вы изменили выражение; все в скобках теперь является левым выражением для добавления:

for item in sublist:
    (item for sublist in list_of_lists)  # added to the list

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

В этом случае должен быть предварительно существующий sublist объект, чтобы это работало вообще; внешний цикл не окончен list_of_lists больше, в конце концов.

Ваша последняя попытка означает:

for sublist in (list_of_lists for item in sublist):
    item  # aded to the list

Вот list_of_lists является элементом цикла в выражении генератора, зацикливающегося на for item in sublist, Снова, sublist должен существовать уже, чтобы это работало. Затем цикл добавляет уже существующий item к окончательному выводу списка.

В твоем случае видимо sublist список из 3 элементов; Ваш окончательный список содержит 3 элемента. item был связан с 5так ты получил 3 раза 5 в вашем выводе.

Понимание списка

Когда я впервые начал понимать список, я прочел это как английские предложения и смог легко их понять. Например,

[item for sublist in list_of_lists for item in sublist]

можно читать как

for each sublist in list_of_lists and for each item in sublist add item

Также фильтрующую часть можно прочитать как

for each sublist in list_of_lists and for each item in sublist add item only if it is valid

И соответствующее понимание будет

[item for sublist in list_of_lists for item in sublist if valid(item)]

Генераторы

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

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

С этим базовым пониманием, если мы посмотрим на ваши выражения в вопросе,

[(item for sublist in list_of_lists) for item in sublist]

Вы смешиваете понимание списка с выражениями генератора. Это будет читаться так

for each item in sublist add a generator expression which is defined as, for every sublist in list_of_lists yield item

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

Кроме того, в последнем случае

[item for sublist in (list_of_lists for item in sublist)]
for each sublist in the generator expression, add item and the generator expression is defined as for each item in sublist yield list_of_lists.

Цикл for будет повторять любую итерацию со следующим протоколом. Таким образом, выражение генератора будет оценено и item всегда будет последним элементом в итерации sublist и вы добавляете это в список. Это также приведет к ошибке времени выполнения, поскольку подсписок еще не определен.

Понимание списка работает так:

[<what i want> <for loops in the order you'd write them naturally>]

В этом случае, <what I want> каждый item в каждом sublist, Чтобы получить эти элементы, вы просто перебираете подсписки в исходном списке и сохраняете / выдает каждый элемент в подсписке. Таким образом, порядок циклов for в понимании списка такой же, как если бы вы не использовали понимание списка. Единственная запутанная часть заключается в том, что <what I want> идет первым, а не внутри тела последнего цикла.

Другие вопросы по тегам