Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Да будет холивар! Использовать args/kwargs или не использовать, это показатель профессионализма или базовые знания, без которых должно быть стыдно? Часто листая github различных проектов натыкаешься на наличие данных аргументов в функциях просто потому что. И данная статья натолкнула на пообщаться на эту тему. Вообще, стараюсь не использовать неконтролируемое аргументирование на практике, но на сколько это вообще распространенный метод программирования? Делитесь в комментариях. И приятного чтения.
В Интернете полно обучалок, которые научат вас использовать *args
и **kwargs
при определении функции в Python. Возможно, вы уже часами пытаетесь понять, как раскрыть их потенциал. Может быть, после всего этого исследования вы теперь чувствуете в них уверенность.
Не надо!
Мощные инструменты опасны. Вы могли облегчить свою жизнь, но запомните мои слова, они вскоре вернутся, чтобы преследовать вас.
Но почему?
Немного основ
Параметры в Python функции могут принимать два типа аргументов:
Позиционные аргументы, которые передаются позиционно.
Именованные аргументы, которые передаются с ключевыми словами.
def foo(start, end):
print(start, end)
Например, foo ('Hi', end = 'Bye!')
Передает позиционный аргумент "Hi"
и именованный аргумент "Bye!"
, где ключевым словом выступает end
для функции foo
. Параметры функции предопределены; количество параметров, принимаемых в функции, фиксировано. Однако это не всегда так.
*args
позволяет передавать вашей функции произвольное количество позиционных аргументов. Звездочка *
- параметр распаковки. Параметры упаковываются в виде итерируемого кортежа внутри функции.
def foo(*args):
print(type(args))
for arg in args:
print(arg)
foo(1, 2, 'end')
# <class 'tuple'>
# 1
# 2
# end
С другой стороны, **kwargs
позволяет передавать вашей функции произвольное количество аргументов с ключевыми словами. Поскольку у каждого именованного аргумента есть ключевое слово и значение, он сгруппирован как итерируемый словарь внутри функции.
def foo2(**kwargs):
print(type(kwargs))
for keyword, value in kwargs.items():
print(f'{keyword}={value}')
foo2(a=1, b=2, z='end')
# <class 'dict'>
# a=1
# b=2
# z=end
Проблема
В большинстве случаев вам действительно не нужны *args
и **kwargs
. Как часто вы не знаете, сколько аргументов должна получить предопределенная ВАМИ функция?
Код гораздо сложнее отлаживать, если вы злоупотребляете в нем подобными решениями, потому что вы позволяете передавать произвольное количество аргументов функции, и функция может вести себя непредсказуемо.
Явное лучше, чем неявное. - Дзен Python
Когда всё-таки использовать аргументы?
Если кратко: используйте их, когда они вам действительно нужны. Например, функция с множеством необязательных полей, и некоторые из них используются только в редких ситуациях. Скажем, функция строит график, и вы можете передавать различные необязательные аргументы, чтобы изменить его цвет, стиль, размер и т.д.
Каждый раз, когда вы используете *args
и/или **kwargs
, убедитесь, что вы пишите к функциям ясную документацию, чтобы избежать путаницы.
Есть один сценарий, в котором их использование может быть неизбежным. Если вы создаете оболочку для функции с неизвестными аргументами, вам нужно будет принять произвольное количество позиционных и именованных аргументов, а затем передать их функции.
Например, декораторы в Python работают как оболочки, которые изменяют поведение кода, не изменяя сам код функции, тем самым расширяя дополнительные функции.
В следующем примере мы создаем трассировку
, которая выводит имя выполняемой функции в качестве проверки работоспособности. Декоратор применяется к функции с помощью @trace
поверх функции, как показано ниже. Поскольку мы хотим применить этот декоратор к любым функциям с любым количеством аргументов, нам нужно использовать *args
и **kwargs
.
def trace(func):
def print_in(*args, **kwargs):
print('Executing function', func.__name__)
return func(*args, **kwargs)
return print_in
@trace
def calc(a,b):
print(f'a+b is {a+b}, a-b is {a-b}.')
calc(1,2)
# Executing function calc
# a+b is 3, a-b is -1.
@trace
def add_all(*args):
print(reduce(lambda a,b:a+b, args))
a = add_all(1,2,3,4,5)
# Executing function add_all
# 15
Выводы
По возможности избегайте их.
Обратите внимание, что args
и kwargs
так называются просто по соглашению. Вы можете называть их как хотите. Именно звездочки *
и **
дают функциям ту самую мощь.