Распаковка, расширенная распаковка и вложенная расширенная распаковка

Рассмотрим эти выражения... Пожалуйста, будьте терпеливы... это длинный список...

(Примечание: некоторые выражения повторяются - это просто для представления "контекста" )

a, b = 1, 2                          # simple sequence assignment
a, b = ['green', 'blue']             # list asqignment
a, b = 'XY'                          # string assignment
a, b = range(1,5,2)                  # any iterable will do


                                     # nested sequence assignment

(a,b), c = "XY", "Z"                 # a = 'X', b = 'Y', c = 'Z' 

(a,b), c = "XYZ"                     # ERROR -- too many values to unpack
(a,b), c = "XY"                      # ERROR -- need more than 1 value to unpack

(a,b), c, = [1,2],'this'             # a = '1', b = '2', c = 'this'
(a,b), (c,) = [1,2],'this'           # ERROR -- too many values to unpack


                                     # extended sequence unpacking

a, *b = 1,2,3,4,5                    # a = 1, b = [2,3,4,5]
*a, b = 1,2,3,4,5                    # a = [1,2,3,4], b = 5
a, *b, c = 1,2,3,4,5                 # a = 1, b = [2,3,4], c = 5

a, *b = 'X'                          # a = 'X', b = []
*a, b = 'X'                          # a = [], b = 'X'
a, *b, c = "XY"                      # a = 'X', b = [], c = 'Y'
a, *b, c = "X...Y"                   # a = 'X', b = ['.','.','.'], c = 'Y'

a, b, *c = 1,2,3                     # a = 1, b = 2, c = [3]
a, b, c, *d = 1,2,3                  # a = 1, b = 2, c = 3, d = []

a, *b, c, *d = 1,2,3,4,5             # ERROR -- two starred expressions in assignment

(a,b), c = [1,2],'this'              # a = '1', b = '2', c = 'this'
(a,b), *c = [1,2],'this'             # a = '1', b = '2', c = ['this']

(a,b), c, *d = [1,2],'this'          # a = '1', b = '2', c = 'this', d = []
(a,b), *c, d = [1,2],'this'          # a = '1', b = '2', c = [], d = 'this'

(a,b), (c, *d) = [1,2],'this'        # a = '1', b = '2', c = 't', d = ['h', 'i', 's']

*a = 1                               # ERROR -- target must be in a list or tuple
*a = (1,2)                           # ERROR -- target must be in a list or tuple
*a, = (1,2)                          # a = [1,2]
*a, = 1                              # ERROR -- 'int' object is not iterable
*a, = [1]                            # a = [1]
*a = [1]                             # ERROR -- target must be in a list or tuple
*a, = (1,)                           # a = [1]
*a, = (1)                            # ERROR -- 'int' object is not iterable

*a, b = [1]                          # a = [], b = 1
*a, b = (1,)                         # a = [], b = 1

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
(a,b), *c = 1,2,3                    # ERROR - 'int' object is not iterable
(a,b), *c = 'XY', 2, 3               # a = 'X', b = 'Y', c = [2,3]


                                     # extended sequence unpacking -- NESTED

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
*(a,b), c = 1,2,3                    # a = 1, b = 2, c = 3

*(a,b) = 1,2                         # ERROR -- target must be in a list or tuple
*(a,b), = 1,2                        # a = 1, b = 2

*(a,b) = 'XY'                        # ERROR -- target must be in a list or tuple
*(a,b), = 'XY'                       # a = 'X', b = 'Y'

*(a, b) = 'this'                     # ERROR -- target must be in a list or tuple
*(a, b), = 'this'                    # ERROR -- too many values to unpack
*(a, *b), = 'this'                   # a = 't', b = ['h', 'i', 's']

*(a, *b), c = 'this'                 # a = 't', b = ['h', 'i'], c = 's'

*(a,*b), = 1,2,3,3,4,5,6,7           # a = 1, b = [2, 3, 3, 4, 5, 6, 7]

*(a,*b), *c = 1,2,3,3,4,5,6,7        # ERROR -- two starred expressions in assignment
*(a,*b), (*c,) = 1,2,3,3,4,5,6,7     # ERROR -- 'int' object is not iterable
*(a,*b), c = 1,2,3,3,4,5,6,7         # a = 1, b = [2, 3, 3, 4, 5, 6], c = 7
*(a,*b), (*c,) = 1,2,3,4,5,'XY'      # a = 1, b = [2, 3, 4, 5], c = ['X', 'Y']

*(a,*b), c, d = 1,2,3,3,4,5,6,7      # a = 1, b = [2, 3, 3, 4, 5], c = 6, d = 7
*(a,*b), (c, d) = 1,2,3,3,4,5,6,7    # ERROR -- 'int' object is not iterable
*(a,*b), (*c, d) = 1,2,3,3,4,5,6,7   # ERROR -- 'int' object is not iterable
*(a,*b), *(c, d) = 1,2,3,3,4,5,6,7   # ERROR -- two starred expressions in assignment


*(a,b), c = 'XY', 3                  # ERROR -- need more than 1 value to unpack
*(*a,b), c = 'XY', 3                 # a = [], b = 'XY', c = 3
(a,b), c = 'XY', 3                   # a = 'X', b = 'Y', c = 3

*(a,b), c = 'XY', 3, 4               # a = 'XY', b = 3, c = 4
*(*a,b), c = 'XY', 3, 4              # a = ['XY'], b = 3, c = 4
(a,b), c = 'XY', 3, 4                # ERROR -- too many values to unpack

Как вы понимаете такую ​​сложность и путаницу. Как всегда можно ПРАВИЛЬНО при расчете результатов таких выражений вручную. Или, читая какой-то другой код, я должен просто игнорировать их и никогда не пытаться понять, что на самом деле делает выражение?

Ответ 1

Мои извинения за длину этого сообщения, но я решил выбрать полноту.

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

Только для распаковки в правой части = (т.е. для rvalues) действуют следующие подстановки:

'XY' -> ('X', 'Y')
['X', 'Y'] -> ('X', 'Y')

Если вы обнаружите, что значение не распаковывается, вы отмените замену. (Подробнее см. Ниже).

Кроме того, когда вы видите "голые" запятые, притворяйтесь там кортежем верхнего уровня. Сделайте это как с левой, так и с правой стороны (то есть для lvalues ​​и rvalues):

'X', 'Y' -> ('X', 'Y')
a, b -> (a, b)

С учетом этих простых правил, вот несколько примеров:

(a,b), c = "XY", "Z"                 # a = 'X', b = 'Y', c = 'Z'

Применяя приведенные выше правила, мы преобразуем "XY" в ('X', 'Y') и покрываем голые запятые в parens:

((a, b), c) = (('X', 'Y'), 'Z')

Визуальная переписка здесь делает достаточно очевидным, как работает присвоение.

Вот ошибочный пример:

(a,b), c = "XYZ"

Следуя приведенным выше правилам замены, мы получаем следующее:

((a, b), c) = ('X', 'Y', 'Z')

Это явно ошибочно; вложенные структуры не совпадают. Теперь посмотрим, как это работает для немного более сложного примера:

(a,b), c, = [1,2],'this'             # a = '1', b = '2', c = 'this'

Применяя приведенные выше правила, получим

((a, b), c) = ((1, 2), ('t', 'h', 'i', 's'))

Но теперь из структуры ясно, что 'this' не распаковывается, а назначается непосредственно c. Поэтому мы отменя замену.

((a, b), c) = ((1, 2), 'this')

Теперь посмотрим, что произойдет, когда мы завершим c в кортеж:

(a,b), (c,) = [1,2],'this'           # ERROR -- too many values to unpack

становится

((a, b), (c,)) = ((1, 2), ('t', 'h', 'i', 's'))

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

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

a, *b, c = "X...Y"                   # a = 'X', b = ['.','.','.'], c = 'Y'

Это становится

(a, *b, c) = ('X', '.', '.', '.', 'Y')

Самый простой способ проанализировать это - это работать с концов. 'X' присваивается a, а 'Y' присваивается c. Остальные значения в последовательности помещаются в список и назначаются b.

Lvalues, такие как (*a, b) и (a, *b), являются только особыми случаями выше. У вас не может быть двух операторов * внутри одной последовательности lvalue, потому что это будет неоднозначно. Где значения будут выглядеть примерно так: (a, *b, *c, d) - in b или c? Я рассмотрю вложенный случай в одно мгновение.

*a = 1                               # ERROR -- target must be in a list or tuple

Здесь ошибка довольно понятна. Цель (*a) должна быть в кортеже.

*a, = (1,2)                          # a = [1,2]

Это работает, потому что там голая запятая. Применение правил...

(*a,) = (1, 2)

Так как нет переменных, кроме *a, *a вырывает все значения в последовательности rvalue. Что, если вы замените (1, 2) на одно значение?

*a, = 1                              # ERROR -- 'int' object is not iterable

становится

(*a,) = 1

Опять же, ошибка здесь не требует пояснений. Вы не можете распаковать то, что не является последовательностью, а *a нужно что-то распаковать. Поэтому мы помещаем его в последовательность

*a, = [1]                            # a = [1]

Что эквивалентно

(*a,) = (1,)

Наконец, это общая точка смущения: (1) совпадает с 1 - вам нужна запятая, чтобы отличить кортеж от арифметического утверждения.

*a, = (1)                            # ERROR -- 'int' object is not 

Теперь для гнездования. На самом деле этот пример не был в вашем разделе "NESTED"; возможно, вы не понимали, что он вложен?

(a,b), *c = 'XY', 2, 3               # a = 'X', b = 'Y', c = [2,3]

становится

((a, b), *c) = (('X', 'Y'), 2, 3)

Первое значение в корте верхнего уровня назначается, а оставшиеся значения в корте верхнего уровня (2 и 3) присваиваются c - как и следовало ожидать.

(a,b),c = 1,2,3                      # ERROR -- too many values to unpack
*(a,b), c = 1,2,3                    # a = 1, b = 2, c = 3

Я уже объяснил, почему первая строка вызывает ошибку. Вторая строка глупа, но вот почему она работает:

(*(a, b), c) = (1, 2, 3)

Как объяснялось ранее, мы работаем с концами. 3 присваивается c, а затем остальные значения назначаются переменной с предшествующим ей *, в данном случае (a, b). Таким образом, что эквивалентно (a, b) = (1, 2), который работает, потому что есть правильное количество элементов. Я не могу думать о какой-либо причине, которая когда-либо появлялась в рабочем коде. Аналогично,

*(a, *b), c = 'this'                 # a = 't', b = ['h', 'i'], c = 's'

становится

(*(a, *b), c) = ('t', 'h', 'i', 's')

Работа с концами, 's' назначается c, а ('t', 'h', 'i') назначается (a, *b). Работая снова с концов, 't' назначается a, а ('h', 'i') назначается b как список. Это еще один глупый пример, который никогда не должен появляться в рабочем коде.

Ответ 2

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

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

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

Просто потому, что вы можете написать произвольно сложные выражения, это не значит, что вы должны. Вы можете написать код типа map(map, iterable_of_transformations, map(map, iterable_of_transformations, iterable_of_iterables_of_iterables)), но вы не.

Ответ 3

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

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

Я предпочитаю использовать распаковку только для простых задач, таких как своп.