Photo by Matthew Henry on Unsplash
Photo by Matthew Henry on Unsplash

Да будет холивар! Использовать 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
Photo by Susan Holt Simpson on Unsplash
Photo by Susan Holt Simpson on Unsplash

Проблема

В большинстве случаев вам действительно не нужны *args и **kwargs. Как часто вы не знаете, сколько аргументов должна получить предопределенная ВАМИ функция?

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

Явное лучше, чем неявное. - Дзен Python


Когда всё-таки использовать аргументы?

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

Каждый раз, когда вы используете *args и/или **kwargs, убедитесь, что вы пишите к функциям ясную документацию, чтобы избежать путаницы.

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

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

Photo by Alexander Schimmeck on Unsplash
Photo by Alexander Schimmeck on Unsplash

В следующем примере мы создаем трассировку, которая выводит имя выполняемой функции в качестве проверки работоспособности. Декоратор применяется к функции с помощью @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 так называются просто по соглашению. Вы можете называть их как хотите. Именно звездочки * и ** дают функциям ту самую мощь.