Треки
/
Python
Python
/
Салабус
/
Розпакування та множинне присвоєння
Ро

Розпакування та множинне присвоєння у Python

3 вправи

Про концепцію Розпакування та множинне присвоєння

Розпакування означає вилучення елементів колекції, як-от 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).

Caution

*<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 не вийде.

Розпакування

Note

У наведених нижче прикладах використано 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.

Caution

Аргументи потрібно структурувати ось так:

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)]
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Розпакування та множинне присвоєння

Практика заблокована

Розблокуйте ще 3 вправи, щоб практикувати концепцію Розпакування та множинне присвоєння