Розпакування означає вилучення елементів колекції, як-от list, tuple чи dict, за допомогою ітерації.
Розпаковані значення потім можна присвоїти змінним у межах тієї самої інструкції.
Дуже поширений приклад такої поведінки - for item in list, де item по черзі набуває значення кожного елемента list протягом ітерації.
Множинне присвоєння - це здатність присвоїти кілька змінних розпакованим значенням у межах однієї інструкції.
Це дає змогу робити код стислішим і зрозумілішим, і виконують це, розділяючи змінні, які треба присвоїти, комою, наприклад first, second, third = (1,2,3) або for index, item in enumerate(iterable).
Спеціальні оператори * і ** часто використовують у контекстах розпакування.
* можна використати, щоб поєднати кілька list/tuple в один list/tuple, розпакувавши кожен у новий спільний list/tuple.
** можна використати, щоб поєднати кілька словників в один словник, розпакувавши кожен у новий спільний dict.
Коли оператор * використовують без колекції, він пакує кілька значень у list.
Це часто використовують у множинному присвоєнні, щоб зібрати всі «зайві» елементи, які не мають окремих присвоєнь, в одну змінну.
У Python також часто користаються цією поведінкою розпакування й пакування, коли викликають або визначають функції, які приймають довільну кількість позиційних або іменованих аргументів.
Ми часто бачимо, як ці «спеціальні» параметри визначають як def some_function(*args, **kwargs), а «спеціальні» аргументи використовують як some_function(*some_tuple, **some_dict).
*<variable_name> і **<variable_name> не слід плутати з * і **. Хоча * і ** використовують відповідно для множення та піднесення до степеня, *<variable_name> і **<variable_name> використовують як оператори пакування й розпакування.
У множинному присвоєнні кількість змінних ліворуч від оператора присвоєння (=) має збігатися з кількістю значень праворуч.
Щоб розділити значення, використаймо кому ,:
>>> a, b = 1, 2
>>> a
1
Якщо множинне присвоєння отримує неправильну кількість змінних для наведених значень, буде викликано ValueError:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
Множинне присвоєння не обмежується одним типом даних:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
Множинне присвоєння можна використати, щоб обміняти місцями елементи в lists.
Ця практика досить поширена в алгоритмах сортування.
Наприклад:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
Оскільки tuples незмінні, обміняти місцями елементи в tuple не вийде.
У наведених нижче прикладах використано lists, але ці самі поняття стосуються й tuples.
У Python можна розпакувати елементи list/tuple/dict в окремі змінні.
Оскільки значення зʼявляються в lists/tuples у певному порядку, їх розпаковують у змінні в тому самому порядку:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
Якщо є значення, які не потрібні, їх можна позначити за допомогою _:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
Розпакування й присвоєння значень із list/tuple усередині list або tuple (також відоме як вкладені list/tuple) працює так само, як поверхневе розпакування, але часто потребує уточнювачів, щоб прояснити контекст або позицію значення:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
Також можна глибоко розпакувати лише частину вкладеного list/tuple:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
Якщо в розпакуванні змінні розташовані неправильно та/або кількість значень неправильна, ми отримаємо ValueError:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
list/tuple за допомогою *
Коли ми розпаковуємо list/tuple, можна використати оператор *, щоб захопити «зайві» значення.
Це зрозуміліше, ніж зрізати list/tuple (що в деяких ситуаціях менш читабельно).
Наприклад, ми можемо вилучити перший елемент і запакувати решту значень у новий list без першого елемента:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Також можна вилучити значення на початку й у кінці list, згрупувавши всі значення посередині:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
Також можна використати * у глибокому розпакуванні:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
Розпакування словника трохи відрізняється від розпакування list/tuple.
Ітерація по словниках типово відбувається за ключами.
Тож розпаковуючи dict, можна розпакувати лише ключі, а не значення:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
Якщо потрібно розпакувати значення, можна використати метод <dict>.values():
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
Якщо потрібні і ключі, і значення, використайте метод <dict>.items().
<dict>.items() генерує ітероване подання, що містить пари ключ-значення.
Їх можна розпакувати в tuple:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
Пакування - це здатність згрупувати кілька значень в один list, присвоєний змінній.
Це корисно, коли треба розпакувати значення, внести зміни, а потім запакувати результати назад у змінну.
Воно також уможливлює обʼєднання двох або більше lists/tuples/dicts.
list/tuple за допомогою *
Пакування list/tuple можна виконати за допомогою оператора *.
Це запакує всі значення в list/tuple.
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
# A list literal can be used instead, but might not be as readable.
>>> [*combined_fruits_too] = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
Докладніше про використання * і ** дивіться в PEP 3132 і PEP 448.
**
Пакування словника виконують за допомогою оператора **.
Це запакує всі пари ключ-значення з одного словника в інший словник або поєднає два словники разом.
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
* і ** з функціямиКоли ми створюємо функцію, яка приймає довільну кількість аргументів, можна використати *args або **kwargs у визначенні функції.
*args використовують, щоб запакувати довільну кількість позиційних (неіменованих) аргументів, а
**kwargs використовують, щоб запакувати довільну кількість іменованих аргументів.
Використання *args:
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
Використання **kwargs:
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args і **kwargs також можна використовувати в поєднанні одне з одним:
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
Також можна записати параметри перед *args, щоб дозволити конкретні позиційні аргументи.
Тоді окремі іменовані аргументи мають стояти перед **kwargs.
Аргументи потрібно структурувати ось так:
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
Якщо не дотриматися цього порядку, ми отримаємо помилку.
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
Написання аргументів у неправильному порядку призведе до помилки:
>>> def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
Можна використати *, щоб розпакувати list/tuple аргументів у виклик функції.
Це дуже корисно для функцій, які не приймають iterable:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
Використання розпакування з * разом із вбудованою функцією zip() - ще один поширений випадок.
Функція zip() приймає кілька ітерованих обʼєктів і повертає list із tuples, у яких згруповано значення з кожного iterable:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]