Сообщить об ошибке.

Простая и расширенная распаковка списков и кортежей

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

a, b, c = iterable

Эта инструкция, в которой требуется, чтобы объект iterable был итерируемым и содержал ровно три элемента, связывает а с первым элементом, b - со вторым и c - с третьим. Операции такого рода носят название присваивание с распаковкой. Выражение в правой части должно быть итерируемым и содержать ровно столько элементов, сколько указано ссылок в левой части, в противном случае Python бросает исключение. Каждая из ссылок, указанных в левой части, связывается с соответствующим элементом из правой части.

Присваивание с распаковкой можно использовать для обмена значениями переменных:

a, b = b, a

Данная инструкция присваивания повторно связывает имя a со значением, с которым было связано имя b, и наоборот.

Если имеется несколько целевых ссылок, то разрешается помечать одну из них символом * - звездочка. Целевая ссылка, которой предшествует символ *, связывается со списком всех элементов, оставшихся не связанными с другими целевыми ссылками.

first, *middle, last = iterable

# что эквивалентно записи без '*' звездочки (расширенной распаковки)

first, middle, last = iterable[0], iterable[1:-1], iterable[-1]

В обеих инструкциях присваивания требуется, чтобы объект iterable имел по крайней мере два элемента. Эту форму операции присваивания называют расширенной распаковкой.

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

Например, используя множественное присваивание дважды, можно сделать следующее:

>>> colour_info = ("AliceBlue", (240, 248, 255))
>>> name, rgb_values = colour_info
>>> name
# 'AliceBlue'
>>> r, g, b = rgb_values
>>> g
# 248

Но если необходимо получить отдельные значения RGB, то можно сразу использовать глубокую распаковку:

>>> colour_info = ("AliceBlue", (240, 248, 255))
>>> name, (r, g, b) = colour_info
>>> name
# 'AliceBlue'
>>> g
# 248

Обратите внимание, что переменные r, g и b группируются с помощью скобок (), для создания кортежа, имитирующего форму переменной colour_info. Если просто написать name, r, g, b = colour_info, то Python будет пытаться выполнить четыре присваивания, и ожидал бы от переменной colour_info последовательность из 4-х элементов:

>>> colour_info = ("AliceBlue", (240, 248, 255))
>>> name, r, g, b = colour_info
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ValueError: not enough values to unpack (expected 4, got 2)

Глубокая/расширенная распаковка также может использоваться в неявном назначении циклов for/in, она не обязательно должна быть в явном виде со знаком равенства!

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

Например:

def greyscale(colour_info):
    # для вычисления оттенка, функция использует срезы 
    return 0.2126*colour_info[1][0] + 0.7152*colour_info[1][1] + 0.0722*colour_info[1][2]

colours = [
    # !!!Ошибка в задании цвета `AliceBlue`!!!
    ("AliceBlue", (240, 248, 255, 127, 255, 212)),
    ("DarkCyan", (0, 139, 139)),
]

# !!!функция отработала без ошибок!!!
print(greyscale(colours[0]))  
# 246.8046

Однако, если написать функцию, которая использует глубокую/расширенную распаковку, то можно поймать ошибку:

def greyscale(colour_info):
    # код вычисления оттенка более лаконичный и короткий 
    name, (r, g, b) = colour_info
    return 0.2126*r + 0.7152*g + 0.0722*b

colours = [
    # !!!Ошибка в задании цвета `AliceBlue`!!!
    ("AliceBlue", (240, 248, 255, 127, 255, 212)),
    ("DarkCyan", (0, 139, 139)),
]

# вот и ошибка
print(greyscale(colours[0]))  
# ValueError: too many values to unpack (expected 3)

Примеры распаковки/упаковки последовательностей.

>>> a, b, c = 0, 5, 10
>>> a, b, c
# 0 5 10

>>> a, b, c = ['one', 'two', 'three']
>>> a, b, c
# one two three

>>> a, b = b, a
>>> a, b
# two one

>>> a, *b = ['one', 'two', 'three']
>>> a, b
# one ['two', 'three']

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

def reduce(function, list_):
    """Уменьшение списка с помощью функции."""

    if not list_:
        raise TypeError("Cannot reduce empty list.")
    value, *list_ = list_
    while list_:
        val, *list_ = list_
        value = function(value, val)
    return value

Распаковка итерируемых объектов в переменные с помощью расширенной распаковки:

>>> fruits = ['lemon', 'orange', 'banana', 'tomato']

>>> first, second, *orher = fruits
>>> orher
# ['banana', 'tomato']

>>> first, *orher = fruits
>>> orher
# ['orange', 'banana', 'tomato']

>>> first, *middle, last = fruits
>>> middle
# ['orange', 'banana']

Распаковка итерируемых объектов может быть вложенной:

>>> fruits = ['lemon', 'orange', 'banana', 'tomato']
>>> ((first_letter, *other_letter), *other) = fruits
>>> first_letter, other_letter
# l ['e', 'm', 'o', 'n']
>>> other
# ['orange', 'banana', 'tomato']

Теперь представим, что есть список с несколькими цветами и нужно вычислить оттенки серого. Для этого можно использовать глубокую распаковку в цикле for/in (а также используя генератор списка):

colours = [
    ("AliceBlue", (240, 248, 255)),
    ("Aquamarine", (127, 255, 212)),
    ("DarkCyan", (0, 139, 139)),
]
greyscales = [
    round(0.2126*r + 0.7152*g + 0.0722*b, 2) for name, (r, g, b) in colours
]
print(greyscales)
# [246.8, 224.68, 109.45]

Упаковка итерируемых объектов (сложение) в новый список:

>>> fruits = ['lemon', 'orange', 'banana', 'tomato']
>>> x = [*fruits, *reversed(fruits)]
>>> x
# ['lemon', 'orange', 'banana', 'tomato', 'tomato', 'banana', 'orange', 'lemon']

>>>x = [*fruits[2:], *fruits[:2]]
>>> x
# ['banana', 'tomato', 'lemon', 'orange']

# Здесь создадим кортеж
>>> x = (*fruits[1:], fruits[0])
>>> x
# ('orange', 'banana', 'tomato', 'lemon')