Як використовувати оператори розпакування (*, **) у Python?

Розпакування у Python: Повний посібник

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

Можливо, ви вже бачили символи * та ** у коді інших розробників, або навіть використовували їх, не до кінця розуміючи їх призначення. Ми детально розберемо концепцію розпакування та навчимо вас, як застосовувати її для написання більш елегантного та “пітонічного” коду.

Для кращого розуміння матеріалу, варто ознайомитися з наступними поняттями:

  • Ітерація: будь-яка послідовність, по якій можна проходити циклом for, наприклад, множини, списки, кортежі та словники.
  • Викликаний об’єкт: об’єкт Python, який можна викликати за допомогою круглих дужок (), наприклад, myfunction().
  • Оболонка: інтерактивне середовище виконання, де можна запускати код Python. Для запуску достатньо ввести “python” у терміналі.
  • Змінна: символічна назва, що зберігає об’єкт та має зарезервовану область пам’яті.

Почнемо з частої плутанини: зірочки в Python також є арифметичними операторами. Одна зірочка використовується для множення, а дві (**) – для піднесення до степеня.

>>> 3*3
9
>>> 3**3
27

Переконаємося в цьому, відкривши оболонку Python і ввівши:

Примітка: для виконання цього посібника вам потрібен Python 3. Якщо у вас його немає, перегляньте інструкцію з встановлення Python.

Як ви бачите, ми ставимо зірочку між числами. Це означає, що ми використовуємо арифметичні оператори.

>>> *range(1, 6),
(1, 2, 3, 4, 5)
>>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2}
{'vanilla': 3, 'chocolate': 2, 'strawberry': 2}

З іншого боку, ми використовуємо зірочки (*, **) перед ітерованими об’єктами, щоб розпакувати їх, наприклад:

Не хвилюйтеся, якщо вам це поки не зовсім зрозуміло, це лише вступ до розпакування. Прочитайте весь посібник, щоб зрозуміти все до кінця!

Що ж таке розпакування?

Розпакування – це процес отримання елементів з ітерованих об’єктів, таких як списки, кортежі та словники. Уявіть, що ви відкриваєте коробку та дістаєте з неї різні предмети, наприклад, кабелі, навушники чи USB-накопичувачі.

Розпакування в Python схоже на розпакування коробки в реальному житті.

>>> mybox = ['cables', 'headphones', 'USB']
>>> item1, item2, item3 = mybox

Розглянемо цей приклад на коді для кращого розуміння:

Як ви бачите, ми призначаємо три елементи зі списку mybox трьом змінним item1, item2, item3. Таке призначення змінних є базовою концепцією розпакування в Python.

>>> item1
'cables'
>>> item2
'headphones'
>>> item3
'USB'

Якщо ви спробуєте вивести значення кожної змінної, ви побачите, що item1 містить ‘cables’, item2 – ‘headphones’ і так далі.

>>> newbox = ['cables', 'headphones', 'USB', 'mouse']
>>> item1, item2, item3 = newbox
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)

На даному етапі з кодом все в порядку, але що, якщо ми хочемо розпакувати список з більшою кількістю елементів у ту ж кількість змінних?

Скоріш за все ви очікували помилку. Фактично, ми намагаємося присвоїти 4 елементи списку трьом змінним. Як Python має визначити, які значення куди присвоїти? Саме тому ми отримуємо ValueError з повідомленням “занадто багато значень для розпакування”. Це відбувається через невідповідність кількості змінних зліва та значень списку справа.

>>> lastbox = ['cables', 'headphones']
>>> item1, item2, item3 = lastbox
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

Якщо ж спробувати виконати аналогічну операцію з більшою кількістю змінних, ніж значень, ви отримаєте іншу помилку ValueError, але з дещо іншим повідомленням.

Примітка: Ми працювали зі списками, але цей спосіб розпакування можна застосовувати до будь-яких ітерованих об’єктів (списків, множин, кортежів, словників).

Отже, як нам вийти з такої ситуації? Чи є спосіб розпакувати усі елементи ітерованого об’єкта у декілька змінних, не отримуючи жодних помилок?

Звичайно, є. Для цього використовується оператор розпакування або оператор зірочки (*, **). Розглянемо, як його застосовувати в Python.

Розпакування списків з використанням оператора *

Оператор зірочки

>>> first, *unused, last = [1, 2, 3, 5, 7]
>>> first
1
>>> last
7
>>> unused
[2, 3, 5]

використовується для збору всіх значень ітерованого об’єкта, які ще не були присвоєні.

>>> first, *_, last = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

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

>>> first, *_, last = [1, 2]
>>> first
1
>>> last
2
>>> _
[]

Як ви розумієте, ми отримуємо всі невикористані значення за допомогою оператора зірочки. Для ігнорування значень краще використовувати змінну підкреслення (_), яка часто використовується як “фіктивна змінна”.

Цей прийом працює навіть тоді, коли список містить лише два елементи:

В цьому випадку фіктивна змінна зберігає порожній список, щоб інші дві змінні могли отримати доступ до значень зі списку.

>>> *string = 'PythonIsTheBest'

Поширені проблеми

>>> *string = 'PythonIsTheBest'
  File "<stdin>", line 1
SyntaxError: starred assignment target must be in a list or tuple

Можна розпакувати унікальний елемент ітерованого об’єкта. Наприклад, ви могли б спробувати щось схоже:Однак, код вище викличе SyntaxError:Це пов’язано з тим, що згідно з

Специфікацією PEP

>>> *string, = 'PythonIsTheBest'
>>> string
['P', 'y', 't', 'h', 'o', 'n', 'I', 's', 'T', 'h', 'e', 'B', 'e', 's', 't']

кортеж (або список) зліва від знака присвоєння

>>> *numbers, = range(5)
>>> numbers
[0, 1, 2, 3, 4]

Якщо ми хочемо розпакувати всі значення ітерованого об’єкта в одну змінну, потрібно встановити кортеж. Для цього достатньо додати просту кому:

Іншим прикладом може бути використання функції range, яка повертає послідовність чисел.

Тепер, коли ви знаєте, як розпаковувати списки та кортежі за допомогою зірочки, можна переходити до розпакування словників.

Розпакування словників за допомогою оператора **

>>> **greetings, = {'hello': 'HELLO', 'bye':'BYE'} 
...
SyntaxError: invalid syntax

У той час як одна зірочка використовується для розпакування списків та кортежів, подвійна зірочка (**) застосовується для розпакування словників.

>>> food = {'fish':3, 'meat':5, 'pasta':9} 
>>> colors = {'red': 'intensity', 'yellow':'happiness'}
>>> merged_dict = {**food, **colors}
>>> merged_dict
{'fish': 3, 'meat': 5, 'pasta': 9, 'red': 'intensity', 'yellow': 'happiness'}

На жаль, ми не можемо розпакувати словник в одну змінну, як ми це робили з кортежами та списками. Тобто наступне викличе помилку:

Проте, ми можемо використовувати оператор ** всередині викликаних об’єктів та інших словників. Наприклад, щоб створити об’єднаний словник з інших словників, можна скористатися наступним кодом:

Це досить простий спосіб створення об’єднаних словників, хоча це і не головна сфера застосування розпакування в Python.

Розглянемо, як використовувати розпакування з викликаними об’єктами.

Передача аргументів у функції: args та kwargs

Можливо, ви вже бачили реалізацію args та kwargs в класах або функціях. Розглянемо, чому потрібно їх використовувати у поєднанні з викликаними об’єктами.

>>> def product(n1, n2):
...     return n1 * n2
... 
>>> numbers = [12, 1]
>>> product(*numbers)
12

Розпакування з оператором * (args)

>>> product(12, 1)
12

Припустимо, є функція, яка обчислює добуток двох чисел.

>>> numbers = [12, 1, 3, 4]
>>> product(*numbers)
...
TypeError: product() takes 2 positional arguments but 4 were given

Як ви бачите, ми розпаковуємо список numbers у функцію. Таким чином, ми фактично виконуємо наступне:

>>> def product(*args):
...     result = 1
...     for i in args:
...             result *= i
...     return result
...
>>> product(*numbers)
144

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

Можна вирішити цю проблему, запакувавши список безпосередньо у функції. Це дозволить ітерувати по його елементах та передавати будь-яку кількість аргументів.

Тут ми сприймаємо параметр args як ітерований об’єкт, проходимо по його елементах і повертаємо добуток усіх чисел. Зверніть увагу, що початкове значення результату повинне бути одиницею, оскільки, якщо ми почнемо з нуля, функція завжди повертатиме нуль. Примітка: args – це лише домовленість, ви можете використовувати будь-яку іншу назву параметра.Можна також передавати довільні числа у функцію без списку, як і у вбудовану

>>> product(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

функцію друку

>>> def test_type(*args):
...     print(type(args))
...     print(args)
... 
>>> test_type(1, 2, 4, 'a string')
<class 'tuple'>
(1, 2, 4, 'a string')

.

Наостанок, давайте дізнаємось тип об’єкта arguments у функції.

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

Упаковка з оператором ** (kwargs)

>>> def make_person(name, **kwargs):
...     result = name + ': '
...     for key, value in kwargs.items():
...             result += f'{key} = {value}, '
...     return result
... 
>>> make_person('Melissa', id=12112, location='london', net_worth=12000)
'Melissa: id = 12112, location = london, net_worth = 12000, '

Як ми вже бачили, оператор ** використовується виключно для словників. Тобто, за допомогою цього оператора ми можемо передавати пари ключ-значення як параметри функції.

Давайте створимо функцію make_person, яка приймає позиційний аргумент “ім’я” і невизначену кількість аргументів з ключовими словами.

Як бачите, оператор **kwargs перетворює всі аргументи з ключовими словами у словник, з яким можна працювати у функції.

>>> def test_kwargs(**kwargs):
...     print(type(kwargs))
...     print(kwargs)
... 
>>> test_kwargs(random=12, parameters=21)
<class 'dict'>
{'random': 12, 'parameters': 21}

Примітка: kwargs – це лише домовленість, ви можете називати цей параметр як завгодно.

Можна перевірити тип kwargs так само, як ми це робили з args:

>>> def my_final_function(*args, **kwargs):
...     print('Type args: ', type(args))
...     print('args: ', args)
...     print('Type kwargs: ', type(kwargs))
...     print('kwargs: ', kwargs)
... 
>>> my_final_function('Python', 'The', 'Best', language="Python", users="A lot")
Type args:  <class 'tuple'>
args:  ('Python', 'The', 'Best')
Type kwargs:  <class 'dict'>
kwargs:  {'language': 'Python', 'users': 'A lot'}

Внутрішня змінна kwargs завжди стає словником, який зберігає пари ключ-значення, передані функції.

Наостанок, давайте використаємо args та kwargs в одній функції:

Висновок

  • Оператори розпакування справді корисні у повсякденній роботі. Тепер ви знаєте, як їх використовувати як в окремих операціях, так і в параметрах функцій.
  • У цьому посібнику ви навчилися:
  • Використовувати * для кортежів і списків та ** для словників.
  • Застосовувати оператори розпакування у конструкціях функцій та класів.

args використовуються для передачі безключових параметрів у функцію. kwargs використовуються для передачі ключових параметрів функціям.