Для чого потрібні спеціальні параметри зірочки та слешу в Python?

Для чого потрібні спеціальні параметри зірочки та слешу в Python?
Переклад 23 хв. читання
26 вересня 2023

Коли ви думаєте про оператор зірочки * у Python, ви, швидше за все, думаєте про множення або піднесення до степеня. Аналогічно, оператор слеш /, ймовірно, асоціюється у вас з діленням. Але ви також можете використовувати зірочку і слеш як спеціальні параметри у заголовках функцій. Вони роблять щось зовсім не пов'язане з математикою.

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

def strange_function(*, x, y):
    ...

def another_strange_function(a, b, /, c, *, d):
    ...

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

Коротко: Зірочка та слеш у Python: контролюють як передавати значення у функції

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

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

Ліва сторона Розділювач Права сторона
Позиційні аргументи / Позиційні або ключові аргументи
Аргументи за позицією або ключовим словом * Аргументи тільки за ключовим словом

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

Припустимо, ви пишете функцію asterisk_usage(), одним з параметрів якої є зірочка. Потім ви викликаєте її:

>>> def asterisk_usage(either, *, keyword_only):
...     print(either, keyword_only)
...
>>> asterisk_usage(either="Frank", keyword_only="Dean")
Frank Dean

>>> asterisk_usage("Frank", keyword_only="Dean")
Frank Dean

>>> asterisk_usage("Frank", "Dean")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: asterisk_usage() takes 1 positional argument but 2 were given

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

Третій виклик функції завершився невдало, оскільки ви намагалися передати другий аргумент за позицією. Повідомлення про помилку вказує на те, що ви використали забагато позиційних аргументів.

Примітка: Якщо ви вирішили використовувати зірочку, то не можете використовувати її як кінцевий параметр у вашій функції. Зірочка призначена для того, щоб змусити вас передавати всі наступні параметри за ключовим словом. Якщо ви поставите її останньою, то наступних параметрів бути не може:
>>> def print_three_members(member1, member2, *):
  File "<stdin>", line 1
    def print_three_members(member1, member2, *):
                                              ^
SyntaxError: named arguments must follow bare *

Як ви можете бачити вище, ви не зможете пройти далі рядка визначення функції.

Тепер припустимо, що ви написали функцію slash_usage(), у заголовку якої є слеш. Спробуємо її використати:

>>> def slash_usage(positional_only, /, either):
...     print(positional_only, either)
...
>>> slash_usage("Frank", either="Dean")
Frank Dean

>>> slash_usage(positional_only="Frank", either="Dean")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: slash_usage() got some positional-only arguments
passed as keyword arguments: 'positional_only'

>>> slash_usage("Frank", "Dean")
Frank Dean

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

Примітка: У документації Python * і / позначають як символи, так і спеціальні параметри. У цьому посібнику ви побачите обидва терміни там, де це доречно. Термін оператори буде стосуватися лише їх використання у множенні та діленні. Крім того, термін аргументи означає дані, які ви передаєте у функцію, а термін параметри - заповнювачі заголовка функції, які їх отримують.

PEP 3102 - Аргументи, що позначаються лише ключовими словами визначає використання зірочки в якості аргументу, а PEP 570 - Параметри, що позначаються лише позиційними символами Python визначає слеш в якості параметра, хоча символ також з'являється ненадовго. PEP 570 також використовує слова параметр і аргумент як взаємозамінні.

Тепер ви знаєте, як використовувати зірочку і слеш при визначенні власної функції. Але, можливо, у вас виникли питання про те, які саме функції вони дозволяють писати, і, можливо, ви також замислилися про зірочку в *args. Читайте далі для отримання відповідей!

Чи можна написати функцію, яка приймає тільки ключові слова?

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

У Python аргумент з ключовим словом - це аргумент, який ви передаєте у функцію за допомогою імені її параметра, наприклад, first_name="Dean". Передача аргументів за ключовим словом робить ваш код читабельнішим, а також робить використання функції зрозумілішою для ваших користувачів. Ви можете передавати аргументи за ключовими словами у будь-якому порядку, але вони повинні йти після позиційних аргументів, якщо це можливо.

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

>>> def print_three_members(*, member1, member2, member3):
...     print(f"member1 is {member1}")
...     print(f"member2 is {member2}")
...     print(f"member3 is {member3}")
...
>>> print_three_members(member1="Frank", member2="Dean", member3="Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy

>>> print_three_members(member1="Frank", member3="Dean", member2="Sammy")
member1 is Frank
member2 is Sammy
member3 is Dean

>>> print_three_members("Frank", "Dean", "Sammy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() takes 0 positional arguments but 3 were given

>>> print_three_members("Frank", member3="Dean", member2="Sammy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() takes 0 positional arguments
but 1 positional argument (and 2 keyword-only arguments) were given

Ваша функція print_three_members() приймає тільки аргументи за ключовими словами. Перші два виклики успішно передають усі аргументи за ключовими словами. Кожен з останніх двох викликів призводить до помилки типу TypeError, оскільки ви передали аргументи за позиціями. Розміщення зірочки як першого параметра означає, що ви повинні передати всі аргументи як аргументи за ключовими словами. Повідомлення про помилки покажуть вам, де ви помилилися.

Чи можна написати функцію, яка приймає тільки позиційні аргументи?

У Python будь-який аргумент, який не передається за допомогою ключового слова, передається за позицією. Ви повинні передавати позиційні аргументи у тому ж порядку, в якому ви визначили їх параметри у заголовку функції. Ви завжди передаєте позиційні аргументи перед аргументами з ключовими словами. Деякі вбудовані функції Python вимагають, щоб ви передавали всі аргументи за позиціями. Починаючи з версії Python 3.8, ви також можете застосовувати цю вимогу у власних функціях.

Раніше ви бачили, як потрібно передавати будь-які аргументи для параметрів, визначених перед косою рискою, за позицією. Щоб написати функцію, яка приймає тільки позиційні аргументи, потрібно задати спеціальний параметр слешу останнім:

>>> def print_three_members(member1, member2, member3, /):
...     print(f"member1 is {member1}")
...     print(f"member2 is {member2}")
...     print(f"member3 is {member3}")

>>> print_three_members("Frank", "Dean", "Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy

>>> print_three_members(member1="Frank", member2="Sammy", member3="Dean")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() got some positional-only arguments
passed as keyword arguments: 'member1, member2, member3'

>>> print_three_members("Frank", "Dean", member3="Sammy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() got some positional-only arguments
passed as keyword arguments: 'member3'

Цього разу ви можете передавати member1, member2 та member3 лише за позицією. Перший виклик відповідає цьому критерію і працює, тоді як інші зазнають невдачі, оскільки ви намагалися використати аргументи з ключовими словами. Знову ж таки, повідомлення про помилки допоможуть вам знайти правильне рішення.

Чи пов'язана гола зірочка з *args?

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

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

Припустимо, ви вирішили поекспериментувати з функцією print_three_members(), замінивши зірочку на *args і перейменувавши її на print_varying_members():

>>> def print_varying_members(member1, member2, *args, member3):
...     print(f"member1 is {member1}")
...     print(f"member2 is {member2}")
...     print(f"member3 is {member3}")
...     print(f"*args contains {args}")

У цьому прикладі ви можете передати аргументи, що відповідають параметрам member1 і member2 за позицією, а усі надлишкові аргументи буде упаковано у кортеж args. Однак аргументи для параметра member3 можна передавати лише за ключовим словом:

>>> print_varying_members("Frank", member2="Dean", member3="Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy
*args contains ()

Це працює тому, що хоча параметри member1 і member2 мають суміш позиційних і ключових аргументів, ви передали позиційні аргументи першими. Кортеж args порожній, оскільки ви не передали жодних додаткових аргументів, окрім визначених параметрів. Передача всього за ключовим словом також працює, як і раніше:

>>> print_varying_members(member1="Frank", member2="Dean", member3="Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy
*args contains ()

Цього разу ви можете призначити перші два аргументи за ключовими словами. Ви виконали вимогу щодо обов'язкових ключових слів-аргументів для member3. З тієї ж причини, що і раніше, кортеж args залишається порожнім. Однак, якщо ви трохи відхилитеся, все може піти зовсім не так:

>>> print_varying_members(member1="Frank", "Dean", member3="Sammy")
  File "<stdin>", line 1
    print_varying_members(member1="Frank", "Dean", member3="Sammy")
                                                                ^
SyntaxError: positional argument follows keyword argument

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

Суть *args полягає в тому, щоб поглинати будь-які додаткові позиційні аргументи, які ви передаєте і для яких немає визначених параметрів. Однак, якщо ви хочете передати додаткові аргументи, ви повинні переконатися, що все, що ви передаєте до *args включно, також передається за позицією. Ви не можете передати одні аргументи за ключовим словом, а інші за позицією, тому що позиційні аргументи завжди повинні йти перед аргументами за ключовим словом, як ви побачите далі:

>>> print_varying_members("Frank", "Dean", "Peter", "Joey", member3="Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy
*args contains ('Peter', 'Joey')

Цього разу ви передали чотири позиційні аргументи з обов'язковим ключовим словом member3. Ваш код безпечно упакував два додаткові позиційні аргументи у кортеж args. Спробуйте замінити четвертий позиційний аргумент на іменований, наприклад, member4="Joey". Параметр *args не може ніде зберігати цей додатковий іменований аргумент, тому ваш код завершиться з помилкою.

Чи можна використовувати зірочку без інших параметрів?

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

>>> def print_three_members(*):
  File "<stdin>", line 1
    def print_three_members(*):
                            ^
SyntaxError: named arguments must follow bare *

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

Це не стосується *args. Ви можете використовувати його окремо, як ви бачили раніше на прикладі функції get_average(*args). Кортеж args може приймати скільки завгодно позиційних аргументів. Звичайно, ви не можете передавати аргументи за ключовим словом, оскільки у функції не визначено жодних іменованих параметрів.

Чи можна використовувати зірочку і слеш у визначенні функції?

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

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

Щоб побачити це у дії, напишіть функцію print_four_members(), яка містить косу похилу риску і зірочку у правильному порядку:

>>> def print_four_members(member1, member2, /, member3, *, member4):
...     print(f"member1 is {member1}")
...     print(f"member2 is {member2}")
...     print(f"member3 is {member3}")
...     print(f"member4 is {member4}")
...

У наведеному вище прикладі ви повинні передати аргументи для member1 і member2 за позицією через використання слешу. Ви можете передати аргумент member3 як за ключовим словом, так і за позицією, у той час, як member4 ви повинні передати за ключовим словом. Далі ви виконуєте різні тестові приклади:

>>> print_four_members("Frank", "Dean", member3="Sammy", member4="Joey")
member1 is Frank
member2 is Dean
member3 is Sammy
member4 is Joey

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

Тепер спробуйте вказати слеш і зірочку навпаки. Ви отримаєте синтаксичну помилку:

>>> def print_four_members(member1, member2, *, member3, /, member4):
  File "<stdin>", line 1
    def print_four_members(member1, member2, *, member3, /, member4):
                                                         ^
SyntaxError: / must be ahead of *

Ваше визначення функції тут неправильне, оскільки зірочка змушує вас передавати всі наступні параметри за ключовим словом, тоді як / змушує вас передавати попередні параметри за позицією. Виникає плутанина щодо того, як ви збираєтесь передати member3. Python теж не знає, тому він здасться! Як нагадує повідомлення про помилку, слеш має стояти перед зірочкою.

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

>>> def print_three_members(member1, member2, /, *, member3):
...     print(f"member1 is {member1}")
...     print(f"member2 is {member2}")
...     print(f"member3 is {member3}")
...
>>> print_three_members("Frank", "Dean", member3="Sammy")
member1 is Frank
member2 is Dean
member3 is Sammy

>>> print_three_members("Frank", "Dean", "Sammy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() takes 2 positional arguments but 3 were given

>>> print_three_members("Frank", member2="Dean", member3="Sammy")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_three_members() got some positional-only arguments
passed as keyword arguments: 'member2'

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

Як і слід було очікувати, змішування знову призведе до виникнення синтаксичної помилки:

>>> def print_three_members(member1, member2, *, /, member3):
  File "<stdin>", line 1
    def print_three_members(member1, member2, *, /, member3):
                                                 ^
SyntaxError: / must be ahead of *

Чому б не спробувати інші комбінації, щоб закріпити розуміння цих правил?

Чи можна використовувати *args і *?

Пам'ятайте, що і параметр *args, і параметр * змушують вас передавати всі наступні аргументи через ключове слово. Якщо ви спробуєте використати *args до або після *, виникне синтаксична помилка:

>>> def print_three_members(member1, member2, *args, *, member3):
  File "<stdin>", line 1
    def print_three_members(member1, member2, *args, *, member3):
                                                     ^
SyntaxError: * argument may appear only once

>>> def print_three_members(member1, member2, *, *args, member3):
  File "<stdin>", line 1
    def print_three_members(member1, member2, *, *args, member3):
                                                 ^
SyntaxError: * argument may appear only once

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

Навіщо використовувати спеціальні параметри у функції?

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

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

Припустимо, ви пишете просту функцію для створення імені користувача, але змушуєте її приймати лише позиційні аргументи:

>>> def username(fn, ln, /):
...     return ln + fn[0]
...
>>> print(username("Frank", "Sinatra"))
SinatraF

>>> print(username(fn="Frank", ln="Sinatra"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: username() got some positional-only arguments
passed as keyword arguments: 'fn, ln'

Щоб переконатися, що ваша функція username() приймає аргументи тільки за позицією, ви використовуєте слеш як кінцевий параметр. Як ви можете бачити, якщо ви спробуєте передати аргументи за їх ключовими словами, то виклик завершиться невдачею. Сам виклик функції не дуже інтуїтивно зрозумілий через погано названі параметри.

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

Другим поширеним випадком використання є зворотна сумісність. Припустімо, що багато коду використовує вашу оригінальну функцію username(), але ви вирішили її вдосконалити. На додачу до нечітких назв параметрів, у вас з'явилася проблема забезпечення зворотної сумісності нової версії з попередньою. Припустимо, що ви оновили вашу функцію наступним чином:

>>> def username(fn, ln, /, *, initial_last=True):
...     if initial_last:
...         return ln + fn[0]
...     else:
...         return fn[0] + ln
...

Оновлена версія username() все ще потребує двох позиційних аргументів, але вона надає можливість використовувати аргумент з ключовим словом initial_last. На щастя, initial_last - це зрозуміле ім'я, тому користувачі не будуть його неправильно розуміти. Оскільки initial_last має значення за замовчуванням, цей параметр не є обов'язковим. Однак, якщо ви не хочете використовувати значення за замовчуванням, False, ви повинні передати їй свій аргумент за допомогою ключового слова.

Спочатку ви вирішили протестувати функцію, щоб переконатися, що вона дійсно є зворотньо сумісною:

>>> username("Frank", "Sinatra")
SinatraF

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

>>> username("Frank", "Sinatra", initial_last=False)
FSinatra

Для прикладу ви вирішили передати ключовим словом initial_last=False. Знову ж таки, обидва результати виглядають добре.

Нарешті, ви намагаєтеся запустити функцію, передавши всі три аргументи за ключовим словом і позицією:

>>> username(fn="Frank", ln="Sinatra", initial_last=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: username() got some positional-only arguments
passed as keyword arguments: 'fn, ln'

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

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

Висновок

У цьому уроці ви дізналися, що слеш / і зірочка * означають більше, ніж ділення і множення. Спеціальний параметр слеш дозволяє вам примусово передавати певні аргументи за позицією, в той час, як зірочка змушує передавати деякі аргументи за ключовим словом. Коли ви використовуєте обидва символи разом, то слеш має стояти на першому місці, а будь-які параметри, визначені між ними, можуть приймати аргументи за позицією або за ключовим словом.

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

Що ви запам'ятали для себе найбільше або що вам найбільше сподобалося з того, що ви дізналися? Як ви збираєтеся використовувати свої нові навички? Залиште коментар нижче і дайте спільноті знати.

Джерело: What Are Python Asterisk and Slash Special Parameters For?
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація