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

#python #python-3.x #iterable-unpacking #argument-unpacking

Вопрос:

Рассмотрим следующие выражения. Обратите внимание, что некоторые выражения повторяются для представления «контекста».

(это длинный список)

 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. Честно говоря, большинство из них гораздо сложнее, чем то, что вы видите в коде каждый день. Изучите основы распаковки списков/кортежей, и все будет в порядке.

2. Обратите внимание, что они рекурсивны. Так что, если вы не справитесь с первыми несколькими, вы справитесь со всем. Попробуйте заменить, например, *( *a, b) на *x, выясните, что x распаковывает, а затем подключите (*a, b) обратно для x и т. Д.

3. @greengit Я считаю, что обладаю глубокими знаниями Python, и я просто знаю общие правила 🙂 Вам не обязательно знать каждый конкретный случай, вам просто иногда нужно запустить переводчика и что-то проверить.

4. Вау, отличный список. Я действительно не знал о a, *b = 1, 2, 3 том, как распаковывать вещи. Но это Py3k, верно ?

Ответ №1:

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

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

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

 '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'
 

Applying the above rules, we convert "XY" to ('X', 'Y') , and cover the naked commas in 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 .

Значения, подобные (*a, b) и (a, *b) являющиеся лишь частными случаями вышеизложенного. Вы не можете иметь двух * операторов внутри одной последовательности значений, потому что это было бы неоднозначно. Куда бы делись ценности в чем-то подобном (a, *b, *c, d) 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 
 

Теперь о гнездовании. На самом деле этого примера не было в вашем разделе «ВЛОЖЕННЫЕ»; возможно, вы не осознавали, что он был вложенным?

 (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 в виде списка. Это еще один глупый пример, который никогда не должен появляться в рабочем коде.

Комментарии:

1. Поскольку ОП привел длинный список примеров, вполне уместно, чтобы вы дали длинный список объяснений.

Ответ №2:

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

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

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

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

Комментарии:

1. Примечание: Я написал такой код, за исключением нескольких более сложных уровней. Это было задумано только как упражнение и сделано с полным осознанием того, что через три месяца это будет бессмысленно для меня и никогда не будет понятно никому другому. Если я правильно помню, он реализовал точку в полигональном тесте, выполнил некоторые преобразования координат и создал несколько SVG, HTML и JavaScript.

Ответ №3:

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

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

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

Ответ №4:

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

 first_param, rest_param, third_param = param[0], param[1:-1], param[-1]
 

Это утверждение равносильно

 first_param, *rest_param, third_param = param
 

В приведенном выше утверждении выражение со звездочкой используется для «перехвата» всех элементов, которые не назначены «обязательным целям» ( first_param amp; third_param в этом примере)

Использование выражения со звездочками в lhs имеет следующие правила:

  1. не более одного звездного выражения в lhs, иначе распаковка не будет уникальной
 *a,b,*c = range(5) # wrong
*a,b,c = range(5) # right
a,*b,c = range(5) # right
 
  1. Чтобы собрать элементы «rest», выражение со звездочками должно использоваться с обязательными целями. Конечная запятая используется для указания на то, что обязательные цели не существуют
 *a = range(5) # wrong
*a, = range(5) # right
 

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