Перейти до змісту

Упаковка та розпаковка ітерабельних об'єктів

Символ * (зірочка) в Python використовується у інфіксних операторах множення та підведення у ступінь. Але це не єдині способи застосування цього символа, його також використовують у префіксних операторах.

Упаковка переданих у функцію аргументів

Як нам вже відомо, при визначенні функції за допомогою зірочки можна вказати змінну, у яку при виклику функції буде "збиратись" довільна кількість позиційних аргументів. Усі аргументи буде "упаковано" в кортеж. Так працюють багато функцій, наприклад print() — цій функції ми можемо передати довільну кількість аргументів для виводу.

Схоже використовується і **: якщо використовувати цей оператор у оголошенні функції, то він "збере" усі передані іменовані аргументи у словник.

Розпаковка в аргументи функцій

При виклику функцій можна використовувати оператор * для "розпаковки" ітерабельного об'єкта в аргументи:

>>> items = [1, 2, 'string', None]
>>> print(*items)
1 2 string None
>>>

Конструкція *items передає усі елементи списка items як окремі аргументи.

Таку розпаковку можна використовувати декілька раз при виклику функції:

>>> numbers = [1, 2, 3]
>>> names = ['Alice', 'Bob']
>>> print(*names, *numbers)
Alice Bob 1 2 3
>>>

Аналогічно можна розпакувати словник у іменовані аргументи:

>>> format = {'sep': '-', 'end': '| '}
>>> print(*'line', **format)
l-i-n-e| >>>

Розпаковку словників можна використовувати декілька раз, але треба слідкувати щоб ключі словників не повторювались, інакше у функцію буде передано декілька іменованих аргументів з однаковими іменами:

>>> f1 = {'sep': '-', 'end': '|'}
>>> f2 = {'end': '~ '}
>>> print(1, 2, 3, **f1, **f2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print() got multiple values for keyword argument 'end'
>>> f1 = {'sep': '-'}
>>> f2 = {'end': '~ '}
>>> print(1, 2, 3, **f1, **f2)
1-2-3~ >>>

Упаковка при присвоєнні

Ітерабельний об'єкт можна "розпакувати" і присвоїти його частинки окремим змінним. При цьому у змінну перед якою буде стояти зірочка усі значення, кількість яких перевищує кількість змінних, знову буде упаковано:

>>> first, *other = 'string'
>>> first
's'
>>> other
['t', 'r', 'i', 'n', 'g']
>>> first, second, *other = 'string'
>>> first, second, other
('s', 't', ['r', 'i', 'n', 'g'])
>>> first, *_, last = 'string'
>>> first, last
('s', 'g')
>>>

Така розпаковка може бути навіть вкладеною:

>>> (first_letter, *_), *_ = ['Alice', 'Bob']
>>> first_letter
'A'
>>>

Розпаковка в літералах послідовностей

При створенні списків, кортежів та множин за допомогою літералів для розпаковки ітерабельних об'єктів також можна використовувати зірочку:

>>> numbers = [1, 2, 3]
>>> names = ['Alice', 'Bob']
>>>

У списку переставимо перший елемент у кінець і сформуємо кортеж:

>>> new_tuple = (*numbers[1:], numbers[0])
>>> new_tuple
(2, 3, 1)
>>>

Створимо множину розпакувавши список і генератор:

>>> uppercase_names = (name.upper() for name in names)
>>> uppercase_names
<generator object <genexpr> at 0x000001387353ABA0>
>>> all_names = {*names, *uppercase_names, *names}
>>> all_names
{'Bob', 'BOB', 'ALICE', 'Alice'}
>>>

Розпаковка в літералах словників

При створенні словників за допомогою літерала можна використовувати **. Найчастіше застосування — об'єднання двох словників в один:

>>> d1 = dict(a=1, b=2)
>>> d2 = dict(b=22, c=33)
>>> {**d1, **d2}
{'a': 1, 'b': 22, 'c': 33}
>>> {**d2, **d1}
{'b': 2, 'c': 33, 'a': 1}
>>>

Можна отримати копію словника паралельно додаючи нові ключ-значення і перезаписуючи вже існуючі ключі:

>>> date = dict(year=2035, month=1)
>>> birthday = {**date, 'year':2021, 'day':17}
>>> birthday
{'year': 2021, 'month': 1, 'day': 17}
>>>

Повернення функцією декількох значень

Якщо функція має повернути не одне значення, а декількіка, ми можемо об'єднати ці значення у кортеж. Відповідно функція буде повертати кортеж. А оскільки кортеж — структура даних ітерабельна, при присвоєнні значень ми можемо розпакувати такий кортеж на складові.

def fetch_data():
    ...
    return data, error

data, error = fetch_data()
if error is not None:
    process_data(data)

enumerate()

А от було б непогано якби у циклі ми могли б отримати одразу і черговий елемент послідовності, і його індекс?

На допомогу приходить функція enumerate(). В якості аргументу вона приймає послідовність, і повертає теж послідовність, послідовність з кортежів. Кортеж на кожній ітерації містить індекс елемента і його значення:

>>> for i in enumerate('abcdef'):
...   print(i)
...
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')
(4, 'e')
(5, 'f')
>>>

Оскільки ми чітко знаємо кількість елементів кожного кортежу, а саме 2 (індекс і елемент), то кортеж можемо розкласти прямо у заголовку циклу:

>>> for index, value in enumerate('abcdef'):
...   print(index, '-', value)
...
0 - a
1 - b
2 - c
3 - d
4 - e
5 - f
>>>

Функція enumerate() повертає послідовність спеціального типу:

>>> a = enumerate('abcde')
>>> a
<enumerate object at 0x000002A9776A1E10>
>>> type(a)
<class 'enumerate'>
>>> tpl = tuple(enumerate('abcde'))
>>> tpl
((0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'))
>>>

Розглянемо на прикладі. У списку цілих чисел треба усі елменти які менше ніж 5 помножити на 10.

>>> numbers = list(range(10))
>>> numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for index, number in enumerate(numbers):
...     if number < 5:
...             numbers[index] = number * 10
...
>>> numbers
[0, 10, 20, 30, 40, 5, 6, 7, 8, 9]
>>>
Back to top