Когда и почему интерпретатор распутывает, предполагая одинаковую длину подсписков? - PullRequest
0 голосов
/ 15 марта 2019

Я впечатлен тем фактом, что простой оператор Python for может легко распутать список списков, без необходимости numpy.unravel или эквивалентной функции сглаживания. Однако компромисс заключается в том, что я не могу получить доступ к таким элементам списка, как этот:

for a,b,c in [[5],[6],[7]]:
     print(str(a),str(b),str(c))
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 1)

и вместо этого это работает, вплоть до длины-1 [5]:

for a,b,c in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]:
     print(a,b,c)

1 2 3
4 5 6
7 8 9
0 0 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 1)

Логически, не имеет смысла предполагать, что список будет иметь фиксированное количество элементов. Почему же Python позволяет нам предполагать, что список списков всегда будет иметь одинаковое количество элементов?

Я бы хотел знать, что ожидает Python, потому что я хочу предугадывать неправильно отформатированные списки / подсписки.

Я изучил документацию по Python и Stackoverflow, но не нашел причины или того, как это делает интерпретатор.

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

Ответы [ 3 ]

3 голосов
/ 15 марта 2019

Python не знает, вы просто сказали , что ожидать трех элементов, распаковав их в три имени. ValueError говорит: «Вы сказали нам три, но мы нашли суб-итерацию, в которой не было трех элементов, и мы не знаем, что делать».

Python не делает ничего особенного для реализации этого; кроме особых случаев для встроенных типов, таких как tuple (и, вероятно, list), реализация состоит в том, чтобы просто выполнить итерацию итерируемого ожидаемого числа раз и вывести все значения, найденные в стеке интерпретатора, а затем сохранить их на предоставленные имена. Он также пытается выполнить итерацию еще раз (ожидая StopIteration), чтобы вы не игнорировали лишние значения.

В ограниченных случаях вы можете проявить гибкость, указав одно из имен распаковки, начинающееся с *, поэтому вы фиксируете все элементы "не вписываются" в это имя (как list). Это позволяет вам установить минимальное количество элементов, в то время как позволяет больше, например, если вам действительно нужен только первый элемент из вашего второго примера, вы можете сделать:

for a, *_ in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]:
    print(a,b,c)

, где _ - это просто имя, которое, по соглашению, означает: «На самом деле мне не важно это значение, но мне нужно имя-заполнитель».

Другим примером может быть, когда вы хотите первый и последний элемент, но в остальном не заботитесь о середине:

for first, *middle, last in myiterable:
    ...

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

3 голосов
/ 15 марта 2019

Python не принимает списки одинаковой длины, потому что это не только для списков.

Когда вы повторяете for a,b,c in [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]], происходит то, что python возвращает итератор , который будет повторяться (возврат) каждый список значений.

Так что for эквивалентно:

l = [[1,2,3],[4,5,6],[7,8,9],[0,0,0], [5]]

l_iter = iter(l)

a,b,c = next(l_iter)

next(l_iter) будет возвращать каждый элемент из списка, пока не вызовет исполнение StopIteration в соответствии с питономпротокол итерации.

Это означает:

a,b,c = [1,2,3]
a,b,c = [4,5,6]
a,b,c = [7,8,9]
a,b,c = [0,0,0]
a,b,c = [5]

Как вы можете видеть, теперь python не может распаковать [5] в a,b,c, поскольку существует только одно значение.

3 голосов
/ 15 марта 2019

Интерпретатор всегда предполагает, что длина соответствует заданию при распаковке, и просто падает с ValueError, если не совпадает. Цикл for на самом деле очень похож на своего рода «оператор повторного присваивания», где LHS является свободной переменной (ями) цикла, а RHS является итеративным контейнером, дающим последовательные значения для использования на каждом шаге. итерации.

Одно присваивание за итерацию, выполненное в начале тела цикла - в вашем случае это распаковывающее присваивание, которое связывает несколько имен.

Итак, чтобы быть правильно эквивалентным второму примеру, ваш первый пример, который был:

for a,b,c in [[5],[6],[7]]:
    ...

должен был быть написан вместо:

for a, in [[5],[6],[7]]:
    ...

Нет «предвкушения», и не может быть, потому что (в общем случае) вы можете перебирать все что угодно, например, передача данных из сокета.

Чтобы полностью понять, как работает цикл for, очень полезна аналогия с операторами присваивания. Все, что вы можете использовать в левой части оператора присваивания, вы можете использовать в качестве цели в цикле for. Например, это эквивалентно установке d[1] = 2 и т. Д. В dict - и должно давать тот же результат, что и dict(RHS):

>>> d = {}
>>> for k, d[k] in [[1, 2], [3, 4]]: 
...     pass 
...
>>> d
{1: 2, 3: 4}

Это просто набор заданий в четко определенном порядке.

...