Python: Почему для понимания двухмерного списка требуется внешний цикл перед внутренним циклом? - PullRequest
0 голосов
/ 03 марта 2019
table = [[1, 11, 111], [2, 22, 222], [3, 33, 333]]
[cell for cell in row for row in table] # Error
[cell for row in table for cell in row] # [1, 11, 111, 2, 22, 222, 3, 33, 333]

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

Какая логика стоит за ячейкой -> таблица -> строка?Как парсер видит это?

Ответы [ 4 ]

0 голосов
/ 03 марта 2019

Вы могли бы подумать, что понимание - это внутренняя версия цикла for.По крайней мере, я сделал сначала.Но самый простой способ понять это - заметить, что вы вызовете переменную до ее определения в первой попытке.row вызывается до его определения.Так что логически вы получите ошибку.

[cell for cell in row ...]

row не определено

[cell for row in table for cell in row]

Здесь нет проблем

0 голосов
/ 03 марта 2019

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

Адаптировано понимание вложенного списка Pythonиз вложенного цикла for:

for row in table:
    for cell in row:
        cell

Объединить строки:

for row in table: for cell in row: cell

Обернуть его в скобки списка, удалить : и переместить повторяющееся выражение вперед:

[cell for row in table for cell in row]
0 голосов
/ 03 марта 2019

Как анализатор видит это?

Правила синтаксиса для таких выражений в python называются «отображениями».Вы можете найти определение здесь .

comprehension ::=  expression comp_for
comp_for      ::=  ["async"] "for" target_list "in" or_test [comp_iter]
comp_iter     ::=  comp_for | comp_if
comp_if       ::=  "if" expression_nocond [comp_iter]

. Элементы нового контейнера - это те элементы, которые будут получены при рассмотрении каждого из элементов for или if в блоке, вложение слева направо и вычисление выражения для создания элемента при каждом достижении самого внутреннего блока.

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

В качестве примера:

[cell for row in table for cell in row]

Интерпретатор сломает его следующим образом:

expression = "cell"
comp_for1  = "for row in table" + comp_for2
comp_for2  = "for cell in row"

Затем интерпретатор восстановит вложенный цикл в иерархии

comp_for1:
    comp_for2:
        expression
0 голосов
/ 03 марта 2019

Циклы for должны быть такими же, как если бы вы записали их «обычным» способом:

for row in table:
    for cell in row:
        print(cell)

Так что, когда вы извлекаете это в понимание списка, вы оставляете циклы как есть (кроме удаления ":") и просто потяните последнее выражение в начало:

# you can actually "abuse" list comprehensions to have short
# loops like this, even if you don't care about the list being
# generated. It's generally not a great practice though
[print(cell) for row in table for cell in row]

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

[for row in table for cell in row cell]

, но я думаю, что это выглядит еще более запутанным;Труднее сказать, где заканчивается второй цикл и начинается оператор внутри него.В конце концов, это дизайнерское решение, хотя я уверен, что некоторые люди сочтут тот или иной подход более интуитивным.

...