Позиционные и именованные аргументы в функциях Python

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

Чем отличаются позиционные и именованные аргументы?

Позиционные аргументы передаются в вызове функции позиционно (по порядку). Именованные аргументы передаются по имени параметра вместе с соответствующими значениями.

def func(p1, p2, p3):
    print(p1, p2, p3)

В этом примере функция func() принимает три аргумента (p1, p2 и p3) и выводит их на печать. В этой функции не указано явно, как передавать параметры при ее вызове.

Рассмотрим следующие вызовы этой функции (все они допустимы):

# Option 1
func("Here", "we", "go")
# Option 2
func("Here", "we", p3="go")
# Option 3
func(p1="Here", p2="we", p3="go")
# Option 4
func("Here", p2="we", p3="go")
 
--------------------
Here we go
Here we go
Here we go
Here we go

Что ж, на вид все хорошо. По крайней мере, пока вы следуете верному пути.

Правило, которого следует придерживаться

При передаче комбинации позиционных и именованных аргументов необходимо придерживаться одного правила.

Следующий вызов функции приведет к ошибке, поскольку он нарушает синтаксис, установленный Python.

func("Here", p2="we", "go")

Аргументы p1 и p3 передаются позиционно, а p2 передается как именованный аргумент.

...
    func("Here", p2="we", "go")
                              ^
SyntaxError: positional argument follows keyword argument

Как видите, эта программа вызвала ошибку SyntaxError с пояснением, что позиционный аргумент не может быть передан после именованного.

Когда вы передаете в функцию именованный аргумент, все последующие аргументы также должны быть переданы как именованные. Позиционные аргументы всегда должны передаваться перед именованными.

Исключительно позиционные или исключительно именованные аргументы

Одна из лучших практик — указание, что аргументы должны быть только позиционными или только именованными.

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

Вот как этого можно добиться.

def func1(p1, p2, p3, /):
    print(p1, p2, p3)
 
def func2(*, kw1, kw2, kw3):
    print(kw1, kw2, kw3)
 
def func3(pos, /, pos_kw, *, kw):
    print(pos, p_kw, kw)

В этом примере функция func1() принимает только позиционные аргументы из-за косой черты (/) в конце определения функции, а функция func2() – только именованные аргументы из-за звездочки (*) в начале.

Функция func3() принимает аргументы обоих видов: pos – позиционный, pos_kw может быть позиционным или именованным, а kw – именованный.

# Переданы только позиционные аргументы
func1("positional", "only", "arguments")
# Переданы только именованные аргументы
func2(kw1="keyword", kw2="only", kw3="arguments")
# Переданы смешанные аргументы
func3("mixed", "arguments", kw="passed")
 
--------------------
positional only arguments
keyword only arguments
mixed arguments passed

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

Ну а если вы пишете функцию для электронной коммерции, вы можете использовать исключительно именованные параметры, чтобы избежать путаницы между ценой, товаром и так далее.

Необязательные аргументы и аргументы со значением по умолчанию

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

def optional_param(name, age, height=170):
    print(f"Name  : {name}")
    print(f"Age   : {age}")
    print(f"Height: {height}")

В функции optional_param() параметр height имеет значение по умолчанию 170. Если вы не передадите его в вызове функции, Python присвоит ему значение по умолчанию. Это означает, что он стал необязательным аргументом.

optional_param("John", 32)
--------------------
Name  : John
Age   : 32
Height: 170

Вы можете отменить значение по умолчанию, указав другое значение. Это может быть полезно, когда вы хотите установить минимальный или максимальный предел.

Вариативные аргументы

Если вы не хотите (или не можете) указать большой список параметров в определении функции, сделайте так, чтобы она принимала вариативный аргумент (любое количество аргументов).

def send_invitation(*args):
    print("Invitation sent to:")
    for everyone in args:
        print(everyone)

В функцию send_invitation() можно передать любое количество позиционных аргументов благодаря *args. Это выражение – *args – указывает, что функция может принимать любое количество аргументов, а их значения хранятся в кортеже.

send_invitation(
    "John",
    "Max",
    "Cindy"
)
--------------------
Invitation sent to:
John
Max
Cindy

Аналогичным образом, но с помощью **kwargs, можно заставить функцию принимать вариативные именованные аргументы.

def party_items(**kwargs):
    print("Party items:")
    for key, value in kwargs.items():
        print(f"{key}: {value}")
 
party_items(
    Poppers=3,
    Ballons=120,
    Sparkles=10
)

В **kwargs именованные аргументы хранятся как пары ключ/значение в словаре, поэтому вы можете выполнять любые связанные операции над ключами и значениями.

Порядок аргументов

Аргументы должны передаваться в том же порядке, в котором параметры указаны в определении функции.

def details(name, age, occupation):
    print(f"Name      : {name}")
    print(f"Age       : {age}")
    print(f"Occupation: {occupation}")
 
details(32, "John", "Writer")

При выполнении этого кода вы получите имя 32, а возраст – John, поскольку при вызове функции был перепутан порядок аргументов.

Name      : 32
Age       : John
Occupation: Writer

Но если вы передадите значения, используя имена параметров, то в каком бы порядке вы их ни передавали, вы получите желаемый результат.

details(age=32, name="John", occupation="Writer")
--------------------
Name      : John
Age       : 32
Occupation: Writer

Такой подход не является универсальным решением, поскольку все функции разные. Например, можно создать функции, принимающие только позиционные или только именованные аргументы.

Поэтому в итоге всегда стоит передавать аргументы в том порядке, в котором они указаны в определении функции.

Подсказка типа аргумента

Неплохо также указать, какой тип аргумента следует передавать при вызове функции.

def func(arg1: int, arg2: float):
    print(f"{arg1} type: {type(arg1)}")
    print(f"{arg2} type: {type(arg2)}")

При вызове функции func() аргумент arg1 должен быть передан как целое число, а arg2 – как десятичное. Это называется подсказкой типа.

func(2, 3.9)
--------------------
2 type: <class 'int'>
3.9 type: <class 'float'>

При этом не обязательно передавать аргументы именно указанных типов. Можно передавать любой тип, какой захотите, и Python не выдаст ошибку, потому что интерпретатор полностью игнорирует подсказки типов.

func("2", 3.9)
--------------------
2 type: <class 'str'>
3.9 type: <class 'float'>

Python – динамически типизированный язык, поэтому подсказки типов будут не очень полезны, но в некоторых случаях они могут помочь избежать ошибок.

Заключение

Python предоставляет разработчикам много свободы. При этом разработчик должен понимать, как получить максимальную отдачу от языка программирования, в том числе как использовать свой опыт и лучшие практики.

Из этой статьи вы узнали о некоторых лучших практиках использования позиционных и именованных аргументов.

Перевод статьи “Best Practices: Positional and Keyword Arguments in Python”.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *