Упаковка та розпаковка ітерабельних об'єктів
Символ *
(зірочка) в 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]
>>>