Как работают args и kwargs в Python

Использование args и kwargs может быть довольно загадочным, особенно если вы новичок в Python. Давайте вместе выясним все, что вам нужно знать о них.

Что такое *args и **kwargs?

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

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

Мы также увидим, где еще можно использовать *args и **kwargs

Давайте начнем!

Пример использования *args с функцией

Давайте создадим простую функцию, которая вычисляет сумму двух чисел:

def sum(x, y):
    result = x+y
    return result

Затем мы вызовем его с одним, двумя или тремя аргументами, чтобы посмотреть, что произойдет:

Один аргумент

print(sum(1))

Мы получаем следующую ошибку, поскольку функция суммы ожидает два аргумента, а мы передали только один:

Traceback (most recent call last):
   File "/opt/python/codefather/args_kwargs.py", line 25, in 
     print(sum(1))
 TypeError: sum() missing 1 required positional argument: 'y'

Обратите внимание, как интерпретатор Python сообщает нам, что отсутствует 1 обязательный позиционный аргумент (y).

Скоро вы увидите, как позиционные аргументы связаны с *args

Два аргумента

print(sum(1, 2))

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

Три аргумента

print(sum(1, 2, 3))

Мы получаем ошибку, потому что наша функция не знает, как обрабатывать три аргумента:

Traceback (most recent call last):
   File "/opt/python/codefather/args_kwargs.py", line 25, in 
     print(sum(1, 2, 3))
 TypeError: sum() takes 2 positional arguments but 3 were given

Эту проблему можно решить с помощью *args…

Что такое *args?

*args позволяет передавать переменное число позиционных аргументов в функцию Python. Он доступен как кортеж в самой функции.

Давайте внесем несколько изменений в нашу функцию суммы:

  • Добавьте *args в качестве последнего аргумента в сигнатуру функции.
  • Проверьте, является ли args пустым, и если он не пуст, добавьте каждый аргумент в *args к конечному результату.
def sum(x, y, *args):
    print("args value:", args)
    result = x+y

    if args:
        for arg in args:
            result += arg

    return result

При выполнении этого кода мы получаем 6 в качестве результата, а также можем увидеть содержимое кортежа *args:

>>> print(sum(1, 2, 3))
args value: (3,)
6

Кортежи похожи на списки Python, с той лишь разницей, что они неизменяемы.

Как передать **kwargs в функцию

Теперь, когда мы увидели, как работает *args, перейдем к **kwargs.

Что такое **kwargs?

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

Давайте посмотрим, как это применимо к нашей функции суммы…

def sum(x, y, *args, **kwargs):
    print("args value:", args)
    print("kwargs value:", kwargs)

    result = x+y

    if args:
        for arg in args:
            result += arg

    if kwargs:
        for kwarg in kwargs.values():
            result += kwarg

    return result

Мы обновили функцию суммы следующим образом:

  • Примите параметр **kwargs.
  • Распечатать содержание словаря kwargs.
  • Добавьте все значения в **kwargs к окончательной сумме.

Когда мы вызываем функцию:

print(sum(1, 2, 3, number1=6, number2=8))

Вывод следующий:

args value: (3,)
kwargs value: {'number1': 6, 'number2': 8}
20

Вы можете увидеть содержимое словаря kwargs и сумму всех чисел, равную 20.

Видите, как работает **kwargs?

Сейчас…

Чтобы сделать функцию суммы универсальной, мы могли бы удалить x и y, и просто оставить *args и **kwargs в качестве аргументов функции. Попробуйте!

Полный исходный код я приведу в конце этого урока.

А теперь давайте перейдем к чему-то другому…

Зачем использовать имена *args и **kwargs

Исходя из того, что мы увидели до сих пор, можно подумать, что интерпретатор Python точно распознает имена *args и **kwargs.

Давайте проведем небольшой эксперимент…

Я изменил нашу функцию суммы, видите разницу?

def sum(x, y, *custom_args, **custom_kwargs):
    print("args value:", custom_args)
    print("kwargs value:", custom_kwargs)

    result = x+y

    if custom_args:
        for arg in custom_args:
            result += arg

    if custom_kwargs:
        for kwarg in custom_kwargs.values():
            result += kwarg

    return result

Я изменил *args и **kwargs соответственно на *custom_args и **custom_kwargs.

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

Итак, похоже, что названия args и kwargs не имеют особого значения

…так откуда же интерпретатор Python знает, как работать с *custom_args и **custom_kwargs?

Давайте внесем еще одно небольшое изменение…

def sum(x, y, args, kwargs):
   ...
   ...

Я удалил * и ** из args и kwargs. И вывод становится таким:

Traceback (most recent call last):
   File "/opt/python/codefather/args_kwargs.py", line 37, in 
     print(sum(1, 2, 3, number1=6, number2=8))
 TypeError: sum() got an unexpected keyword argument 'number1'

Это показывает, что…

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

Символ * называется итеративным оператором распаковки, а символ **оператором распаковки словаря.

Далее в этом уроке мы подробнее рассмотрим, что означает, что * и ** являются операторами распаковки…

Передача *args и **kwargs в функцию

Теперь мы попробуем что-то другое, чтобы показать, почему мы говорим, что *args содержит позиционные аргументы, а **kwargs содержит ключевые аргументы.

Вернемся к первой версии нашей функции суммы:

def sum(x, y):
    result = x+y
    return result

На этот раз мы хотим использовать *args и **kwargs для передачи аргументов этой функции.

Вот как…

Передача аргументов с помощью *args

Мы определим кортеж, содержащий значения x и y. Затем мы передадим его как *args:

args = (1, 2)
print(sum(*args))

Попробуйте и убедитесь, что результат правильный.

В этом случае значения в кортеже *args присваиваются аргументам x и y в зависимости от их положения. Первый элемент в кортеже присваивается x, а второй элемент — y.

Если мы добавим пару операторов печати к функции суммы, чтобы увидеть значения x и y…

def sum(x, y):
    print("x:", x)
    print("y:", y)
    result = x+y
    return result

…получаем следующий результат:

x: 1
y: 2
3

Передача аргументов с помощью **kwargs

Мы определим словарь, содержащий x и y в качестве ключей. Чтобы показать, что в этом случае позиция (или порядок) элемента словаря не имеет значения, мы укажем ключ y перед ключом x.

Затем мы передадим его как **kwargs в нашу функцию суммы:

kwargs = {'y': 2, 'x': 1}
print(sum(**kwargs))

Если вы выполните код, то снова увидите, что результат правильный.

Значения в словаре **kwargs присваиваются аргументам x и y на основе их имени. Значение для ключа словаря ‘x’ присваивается параметру x функции, и то же самое применяется к y.

Два оператора печати, которые мы добавили ранее в функцию, подтверждают, что значения x и y соответствуют ожидаемым:

x: 1
y: 2
3

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

Какой тип у *args и *kwargs?

Мы уже говорили о том, что *args — это кортеж, а **kwargs — это словарь.

Но сначала давайте не будем принимать это как должное…

…мы будем использовать функцию Python type() для проверки их типа.

def print_args_type(*args, **kwargs):
    print("*args type:", type(args))
    print("**kwargs type:", type(kwargs))

print_args_type(1, 3, number=5)

Вот что получилось:

*args type: <class 'tuple'>
**kwargs type: <class 'dict'>

Итак, то, что мы видели до сих пор, подтверждается, когда речь заходит о типах *args и **kwargs.

А теперь давайте рассмотрим то, что может вызвать синтаксические ошибки в вашем коде Python…

Порядок аргументов для функции Python

При использовании *args и **kwargs вместе со стандартными позиционными или ключевыми аргументами функций необходимо кое-что знать…

Чтобы продемонстрировать эту концепцию, я изменил функцию, которую мы создали в предыдущем разделе:

def print_args_type(*args, **kwargs, arg1):
    print("*args type:", type(args))
    print("**kwargs type:", type(kwargs))
    print("arg1:", arg1)

print_args_type(1, 3, number=5, 6)

Вот изменения, которые я применил к нашей программе:

  • arg1 добавлен в конец списка параметров функции print_args_type().
  • В функцию добавлен оператор print для вывода значения arg1.
  • дополнительный аргумент (6) передается при вызове print_args_type().

Теоретически мы могли бы ожидать, что новый аргумент, переданный при вызове функции, будет присвоен параметру arg1, но вот что мы получаем в ответ:

File "/opt/python/codefather/args_kwargs.py", line 48
     def print_args_type(*args, **kwargs, arg1):
                                             ^
 SyntaxError: invalid syntax

По какой-то причине интерпретатору Python не нравится arg1.

Но почему?

Это связано с тем, что Python устанавливает определенный порядок аргументов функции:

  • Сначала идут позиционные аргументы в следующем порядке: стандартные аргументы, за которыми следуют *args.
  • Ключевые аргументы должны располагаться после позиционных аргументов в следующем порядке: стандартные аргументы, за которыми следуют **kwargs.

Объяснение аргументов и кваргов Python

Это означает, что arg1 должен идти перед *args.

Давайте посмотрим, правда ли это, вот обновленная функция. Обратите внимание, как я также изменил позицию аргумента 6 в вызове функции:

def print_args_type(arg1, *args, **kwargs):
    print("arg1:", arg1)
    print("*args:", args)
    print("**kwargs:", kwargs)

print_args_type(6, 1, 3, number=5)

Вот что получилось:

arg1: 6
*args: (1, 3)
**kwargs: {'number': 5}

Python присваивает число 6 arg1, оставшиеся два позиционных аргумента (1 и 3) *args и единственный ключевой аргумент **kwargs.

Давайте также добавим фиксированный ключевой аргумент, который, как объяснялось ранее, должен располагаться между *args и **kwargs в сигнатуре функции:

def print_args_type(arg1, *args, kw_arg1, **kwargs):
    print("arg1:", arg1)
    print("*args:", args)
    print("kw_arg1:", kw_arg1)
    print("**kwargs:", kwargs)

print_args_type(6, 1, 3, kw_arg1=4, number=5)

И вывод соответствует тому, что мы ожидаем:

arg1: 6
*args: (1, 3)
kw_arg1: 4
**kwargs: {'number': 5}

Имеет ли это смысл?

Объяснение операторов распаковки Python

В какой-то момент этого руководства я упомянул, что * и ** являются операторами распаковки.

Но что это означает на практике?

Начнем с вывода значения кортежа без оператора * и с ним:

args = (1, 2, 3)
print(args)
print(*args)

Вывод:

(1, 2, 3)
1 2 3

Итак, без оператора распаковки мы видим, что печатается полный кортеж…

…когда мы применяем оператор распаковки *, элементы кортежа «распаковываются». Оператор печати печатает три отдельных числа.

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

Оператор распаковки * применяется к итерируемым объектам (например, кортежам и спискам).

Теперь давайте посмотрим, что происходит, когда мы применяем оператор * к словарю:

args = {'arg1': 1, 'arg2': 2}
print(args)
print(*args)

Вывод:

{'arg1': 1, 'arg2': 2}
arg1 arg2

Мы получаем ключи словаря обратно только при применении оператора *, а это не совсем то, что нам нужно.

Для распаковки словарей следует использовать оператор **.

Вот пример использования оператора ** для создания нового словаря, содержащего все пары ключ/значение из двух словарей:

dict1 = {'arg1': 1, 'arg2': 2}
dict2 = {'arg3': 3, 'arg4': 4}
print({**dict1, **dict2})

Получившийся словарь Python:

{'arg1': 1, 'arg2': 2, 'arg3': 3, 'arg4': 4}

Теперь вы знаете, как операторы распаковки могут помочь вам в ваших программах на Python.

Использование *args с лямбда-функцией Python

Вернемся к нашей первоначальной функции суммы:

def sum(x, y):
    result = x+y
    return result

Ниже вы можете увидеть лямбда-представление этой функции:

>>> lambda x,y: x+y
<function <lambda> at 0x101dab680> 

Вот как можно передать два аргумента этой лямбде:

>>> (lambda x,y: x+y)(1,2)
3 

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

…мы можем сделать это с помощью *args:

>>> (lambda *args: sum(args))(1,2,4,5,6)
18 
>>> (lambda *args: sum(args))(3,7,6)
16 

И последнее, для чего можно использовать *args и **kwargs

args и kwargs как часть декоратора

*args и **kwargs также очень полезны для определения декораторов в Python.

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

Вот пример кода:

def verify_user(func):
    def wrapper(*args, **kwargs):
        if not user['logged_in']:
            print("ERROR: User {} is not logged in!".format(user['name']))
            return
        else:
            print("INFO: User {} is logged in".format(user['name']))
            return func(*args, **kwargs)

    return wrapper

Я создал статью о декораторах Python, если вы хотите узнать, как работают декораторы и как использовать *args и **kwargs как часть декораторов.

Заключение

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

Подведем итоги того, что мы рассмотрели:

  • *args позволяет передавать переменной число позиционных аргументов в функцию. Это кортеж, содержащий аргументы в том порядке, в котором они передаются в вызове функции.
  • **kwargs позволяет передавать переменной число ключевых аргументов в функцию. Это словарь, поэтому мы говорим о ключевых аргументах. Потому что аргументы идентифицируются ключом.

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

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

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

Наконец, у нас также есть:

  • просматривался порядок, ожидаемый интерпретатором Python для позиционных и ключевых аргументов.
  • практиковали использование операторов распаковки * и **.

А теперь пришло время вам начать использовать *args и **kwargs.