Представляя *
(звездочку или астериск) в Python, вы, скорее всего, думаете об операторе умножения или возведения в степень. Аналогично, /
(прямая косая черта или слэш), скорее всего, ассоциируется у вас с делением. Но звездочку и косую черту также можно использовать в качестве специальных параметров в функциях. И они никак не связаны с математикой.
При определении функций мы часто включаем список параметров, разделенных запятыми, чтобы определить, какие типы аргументов пользователь может передать нашей функции. Использование символов *
и /
в качестве специальных параметров в заголовке функции поначалу может показаться странным:
def strange_function(*, x, y): ... def another_strange_function(a, b, /, c, *, d): ...
Тем не менее, оба эти определения функций вполне допустимы. Итак, что же означают звездочка и косая черта в определении функции Python?
Содержание
- Вкратце: звездочка и косая черта в Python управляют передачей значений в функции
- Можно ли написать функцию, принимающую только именованные аргументы?
- Можно ли написать функцию, принимающую только позиционные аргументы?
- Связана ли звездочка с *args?
- Можно ли использовать звездочку без других параметров?
- Можно ли использовать и звездочку, и косую черту в определении функции?
- Можно ли использовать *args и звездочку вместе?
- Зачем использовать специальные параметры в функции?
- Заключение
Вкратце: звездочка и косая черта в 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
Параметр either
стоит перед звездочкой. Это значит, что ему можно передавать как именованные, так и позиционные аргументы. Параметр 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
На этот раз мы использовали косую черту, чтобы гарантировать, что все предшествующие аргументы будут позиционными. Когда вы передаете в positional_only
позиционный аргумент, ваш вызов работает. Однако если передать именованный аргумент, вызов завершится неудачей. Финальный вызов функции показывает, что аргумент either
может быть как позиционным, так и именованным.
Примечание. В документации Python символы *
и /
называют и “символами”, и “специальными параметрами”. В этом руководстве мы будем использовать оба термина. Термин «операторы» будет относиться только к их использованию в умножении и делении. Кроме того, термин “аргументы” относится к данным, которые вы передаете в функцию, а “параметры” — к получающим их плейсхолдерам заголовка функции.
PEP 3102 — Keyword-Only Arguments определяет использование звездочки и называет ее “аргументом”, а PEP 570 — Python Positional-Only Parameters определяет использование косой черты и называет ее “параметром”, хотя термин “символ” тоже встречается. В 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
заключается в том, чтобы поглощать любые дополнительные позиционные аргументы, которые вы передаете и для которых нет определенных параметров. Но если вы хотите передать дополнительные аргументы, нужно следить за тем, чтобы все аргументы до них тоже были позиционными. Нельзя передать именованные, а затем позиционные:
>>> 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 *
Если вы попытаетесь написать функцию с одной лишь звездочкой, то все, что вам удастся сделать, — это выдать SyntaxError. Забудьте об этом!
С *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” как позиционный, и вы получите тот же результат.
Теперь попробуйте поменять местами косую черту и звездочку в определении функции. Вы получите ошибку SyntaxError:
>>> 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'
Поэкспериментировав с этим кодом, вы увидите, что первые два аргумента должны быть позиционными а третий — именованным. По-другому просто не получится.
Как и следовало ожидать, перемешивание аргументов снова приводит к ошибке SyntaxError:
>>> 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
до или после звездочки, то возникнет SyntaxError:
>>> 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()
будет принимать исключительно позиционные аргументы, вы используете /
в качестве последнего параметра. Как видите, если попытаться передать именованные аргументы, то вызов завершится неудачей. Сам вызов функции не очень интуитивно понятен из-за плохо названных параметров.
Но даже если параметры вашей функции плохо названы, они все равно всегда скрыты от пользователя. Зная, что читабельность кода имеет значение, вы, вероятно, предпочли бы обновить их, но это может повлечь за собой множество обновлений в теле функции. А это может привести к проблемам. Умное использование косой черты позволило избежать такой необходимости.
Второй распространенный случай использования — обратная совместимость. Предположим, что многие программы используют вашу оригинальную функцию 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'
На этот раз код аварийно завершается. Это хорошо, потому что вы все равно не хотели, чтобы пользователи использовали вашу функцию таким образом.
Еще одно преимущество использования звездочки и косой черты заключается в том, что это добавляет внутреннюю документацию к вашей функции. Включая звездочку и косую черту, вы даете понять всем, кто читает вашу функцию, как именно вы собираетесь передавать ей аргументы. Это позволяет узнать, какие аргументы являются только именованными и только позиционными или могут быть любыми.
Заключение
Из этой статьи вы узнали, что прямая косая черта (/
) и звездочка (*
) в Python означают нечто большее, чем деление и умножение. Специальный параметр /
позволяет вам указать, что некоторые аргументы должны быть исключительно позиционными, а специальный параметр *
— что некоторые аргументы должны быть непременно именованными. Когда вы используете оба символа вместе, косая черта должна стоять первой, а все параметры, определенные между /
и *
, могут принимать как позиционные, так и именованные аргументы.
Вы также увидели, как эти символы полезны при создании функций, которые можно обновлять, сохраняя при этом обратную совместимость. Кроме того, мы рассмотрели, как *args связан со звездочкой.
Перевод статьи “What Are Python Asterisk and Slash Special Parameters For?”.