Вопрос-Ответ

Unpacking, extended unpacking and nested extended 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

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

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

Только для целей распаковки следующие замены допустимы в правой части = (т.Е. Для 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') и закрываем запятые в скобках:

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

first_param, *rest_param, third_param = param

В приведенном выше заявлении выражение со звездочкой используется для "перехвата" всех элементов, которые не назначены "обязательным целям" (first_param & 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.

2023-12-17 15:50 python python-3.x