Аргументы args и kwargs в Python

Оглавление

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

К концу статьи вы будете знать:

  • Что на самом деле означают *args и **kwargs
  • Как использовать *args и **kwargs в определениях функций
  • Как использовать одну звездочку (*) для распаковки повторяющихся объектов
  • Как использовать две звездочки (**) для распаковки словарей

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

Передача нескольких аргументов функции

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

def my_sum(a, b):
    return a + b

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

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

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

sum_integers_list.py

def my_sum(my_integers):
    result = 0
    for x in my_integers:
        result += x
    return result

list_of_integers = [1, 2, 3]
print(my_sum(list_of_integers))

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

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

sum_integers_args.py

def my_sum(*args):
    result = 0
    # Iterating over the Python args tuple
    for x in args:
        result += x
    return result

print(my_sum(1, 2, 3))

В этом примере вы больше не передаете список в my_sum(). Вместо этого вы передаете три разных позиционных аргумента. my_sum() принимает все параметры, которые указаны во входных данных, и упаковывает их все в один повторяемый объект с именем args.

Обратите внимание, что args - это просто имя. От вас не требуется использовать это имя args. Вы можете выбрать любое имя, которое вам больше нравится, например integers:

sum_integers_args_2.py

def my_sum(*integers):
    result = 0
    for x in integers:
        result += x
    return result

print(my_sum(1, 2, 3))

Функция по-прежнему работает, даже если вы передаете итерируемый объект как integers вместо args. Все, что здесь имеет значение, - это то, что вы используете оператор распаковки (*).

Имейте в виду, что повторяемый объект, который вы получите с помощью оператора распаковки *, не является list но tuple. A tuple похож на a list тем, что они оба поддерживают нарезку и итерацию. Однако кортежи сильно отличаются друг от друга, по крайней мере, в одном аспекте: списки изменяемы, в то время как кортежи - нет. Чтобы проверить это, запустите следующий код. Этот скрипт пытается изменить значение списка:

change_list.py

my_list = [1, 2, 3]
my_list[0] = 9
print(my_list)

Значение, расположенное в самом первом индексе списка, должно быть изменено на 9. Если вы запустите этот скрипт, вы увидите, что список действительно изменяется:

$ python change_list.py
[9, 2, 3]

Первым значением будет уже не 0, а обновленное значение 9. Теперь попробуйте проделать то же самое с кортежем:

change_tuple.py

my_tuple = (1, 2, 3)
my_tuple[0] = 9
print(my_tuple)

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

$ python change_tuple.py
Traceback (most recent call last):
  File "change_tuple.py", line 3, in <module>
    my_tuple[0] = 9
TypeError: 'tuple' object does not support item assignment

Это связано с тем, что кортеж является неизменяемым объектом, и его значения не могут быть изменены после присвоения. Помните об этом, когда будете работать с кортежами и *args.

Использование переменной kwargs в Python в определениях функций

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

concatenate.py

def concatenate(**kwargs):
    result = ""
    # Iterating over the Python kwargs dictionary
    for arg in kwargs.values():
        result += arg
    return result

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))

При выполнении скрипта, описанного выше, concatenate() выполнит итерацию по словарю Python kwargs и объединит все найденные значения:

$ python concatenate.py
RealPythonIsGreat!

Нравится args, kwargs - это просто имя, которое можно изменить на любое другое. Опять же, что здесь важно, так это использование оператора распаковки (**).

Итак, предыдущий пример можно было бы записать следующим образом:

concatenate_2.py

def concatenate(**words):
    result = ""
    for arg in words.values():
        result += arg
    return result

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))

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

На самом деле, если вы забудете использовать этот метод, вам придется выполнять итерацию по ключам вашего словаря Python kwargs, как в следующем примере:

concatenate_keys.py

def concatenate(**kwargs):
    result = ""
    # Iterating over the keys of the Python kwargs dictionary
    for arg in kwargs:
        result += arg
    return result

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))

Теперь, если вы попытаетесь выполнить этот пример, вы увидите следующий результат:

$ python concatenate_keys.py
abcde

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

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

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

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

Напомним, что правильный порядок следования ваших параметров таков:

  1. Стандартные аргументы
  2. *args аргументы
  3. **kwargs аргументы

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

correct_function_definition.py

def my_function(a, b, *args, **kwargs):
    pass

Переменная *args соответствующим образом указана перед **kwargs. Но что, если вы попытаетесь изменить порядок следования аргументов? Например, рассмотрим следующую функцию:

wrong_function_definition.py

def my_function(a, b, **kwargs, *args):
    pass

Теперь в определении функции **kwargs стоит перед *args. Если вы попытаетесь запустить этот пример, интерпретатор выдаст сообщение об ошибке:

$ python wrong_function_definition.py
  File "wrong_function_definition.py", line 2
    def my_function(a, b, **kwargs, *args):
                                    ^
SyntaxError: invalid syntax

В этом случае, поскольку *args следует за **kwargs, интерпретатор Python выдает SyntaxError.

Распаковка с помощью операторов: * и **

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

Операторы одинарной и двойной распаковки asterisk были введены в Python 2. Начиная с версии 3.5, они стали еще более мощными благодаря PEP 448. Короче говоря, операторы распаковки - это операторы, которые распаковывают значения из итерируемых объектов в Python. Одиночный оператор asterisk * можно использовать для любой итерации, предоставляемой Python, в то время как двойной оператор asterisk ** можно использовать только для словарей.

Давайте начнем с примера:

print_list.py

my_list = [1, 2, 3]
print(my_list)

Этот код определяет список и затем выводит его на стандартный вывод:

$ python print_list.py
[1, 2, 3]

Обратите внимание на то, как напечатан список, а также на соответствующие скобки и запятые.

Теперь попробуйте добавить оператор распаковки * к названию вашего списка:

print_unpacked_list.py

my_list = [1, 2, 3]
print(*my_list)

Здесь оператор * указывает print() сначала распаковать список.

В этом случае результатом будет уже не сам список, а скорее содержимое списка:

$ python print_unpacked_list.py
1 2 3

Видите ли вы разницу между этим выполнением и выполнением из print_list.py? Вместо списка, print() принял три отдельных аргумента в качестве входных данных.

Еще одна вещь, которую вы заметите, это то, что в print_unpacked_list.py вы использовали оператор распаковки * для вызова функции, а не в определении функции. В этом случае print() принимает все элементы списка так, как если бы они были отдельными аргументами.

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

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

unpacking_call.py

def my_sum(a, b, c):
    print(a + b + c)

my_list = [1, 2, 3]
my_sum(*my_list)

Здесь my_sum() явно указывает, что a, b, и c являются обязательными аргументами.

Если вы запустите этот скрипт, то получите сумму трех чисел в my_list:

$ python unpacking_call.py
6

3 элемента в my_list идеально совпадают с требуемыми аргументами в my_sum().

Теперь рассмотрим следующий скрипт, где my_list имеет 4 аргумента вместо 3:

wrong_unpacking_call.py

def my_sum(a, b, c):
    print(a + b + c)

my_list = [1, 2, 3, 4]
my_sum(*my_list)

В этом примере my_sum() по-прежнему ожидает только три аргумента, но оператор * получает 4 элемента из списка. Если вы попытаетесь выполнить этот скрипт, то увидите, что интерпретатор Python не может его запустить:

$ python wrong_unpacking_call.py
Traceback (most recent call last):
  File "wrong_unpacking_call.py", line 6, in <module>
    my_sum(*my_list)
TypeError: my_sum() takes 3 positional arguments but 4 were given

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

Чтобы протестировать это поведение, рассмотрим следующий пример:

sum_integers_args_3.py

def my_sum(*args):
    result = 0
    for x in args:
        result += x
    return result

list1 = [1, 2, 3]
list2 = [4, 5]
list3 = [6, 7, 8, 9]

print(my_sum(*list1, *list2, *list3))

При запуске этого примера будут распакованы все три списка. Каждый отдельный элемент передается в my_sum(), что приводит к следующему результату:

$ python sum_integers_args_3.py
45

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

extract_list_body.py

my_list = [1, 2, 3, 4, 5, 6]

a, *b, c = my_list

print(a)
print(b)
print(c)

В этом примере my_list содержит 6 элементов. Первой переменной присваивается значение a, последней - c, а все остальные значения помещаются в новый список b. Если вы запустите скрипт , print() это покажет вам, что ваши три переменные имеют ожидаемые значения:

$ python extract_list_body.py
1
[2, 3, 4, 5]
6

Еще одна интересная вещь, которую вы можете сделать с помощью оператора распаковки *, - это разделить элементы любого повторяемого объекта. Это может быть очень полезно, если вам нужно объединить два списка, например:

merging_lists.py

my_first_list = [1, 2, 3]
my_second_list = [4, 5, 6]
my_merged_list = [*my_first_list, *my_second_list]

print(my_merged_list)

Оператор распаковки * добавляется перед обоими my_first_list и my_second_list.

Если вы запустите этот скрипт, то увидите, что в результате получится объединенный список:

$ python merging_lists.py
[1, 2, 3, 4, 5, 6]

Вы даже можете объединить два разных словаря, используя оператор распаковки **:

merging_dicts.py

my_first_dict = {"A": 1, "B": 2}
my_second_dict = {"C": 3, "D": 4}
my_merged_dict = {**my_first_dict, **my_second_dict}

print(my_merged_dict)

Здесь объединяемыми итерациями являются my_first_dict и my_second_dict.

При выполнении этого кода выводится объединенный словарь:

$ python merging_dicts.py
{'A': 1, 'B': 2, 'C': 3, 'D': 4}

Помните, что оператор * работает с любым итерируемым объектом. Его также можно использовать для распаковки строки :

string_to_list.py

a = [*"RealPython"]
print(a)

В Python строки являются итерируемыми объектами, поэтому * распакует их и поместит все отдельные значения в список a:

$ python string_to_list.py
['R', 'e', 'a', 'l', 'P', 'y', 't', 'h', 'o', 'n']

Предыдущий пример кажется замечательным, но при работе с этими операторами важно помнить о седьмом правиле "Дзен Python" Тима Питерса: Важна читабельность..

Чтобы понять, почему, рассмотрим следующий пример:

mysterious_statement.py

*a, = "RealPython"
print(a)

Там есть оператор распаковки *, за которым следуют переменная, запятая и присваивание. Это очень много, уложенное в одну строку! По сути, этот код ничем не отличается от предыдущего примера. Он просто берет строку RealPython и присваивает всем элементам новый список a, благодаря оператору распаковки *.

Запятая после a делает свое дело. Когда вы используете оператор распаковки с присваиванием переменной, Python требует, чтобы результирующая переменная была либо списком, либо кортежем. С помощью завершающей запятой вы определили кортеж только с одной именованной переменной, a, которая представляет собой список ['R', 'e', 'a', 'l', 'P', 'y', 't', 'h', 'o', 'n'].

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

Если вы укажете вторую переменную в левой части задания, Python присвоит второй переменной последний символ строки, одновременно собирая все оставшиеся символы в списке a:

>>> *a, b = "RealPython"

>>> b
"n"

>>> type(b)
<class 'str'>

>>> a
["R", "e", "a", "l", "P", "y", "t", "h", o"]

>>> type(a)
<class 'list'>

Когда вы используете эту операцию со второй именованной переменной, как показано выше, результаты могут быть более привычными, если вы раньше работали с распаковкой кортежей. Однако, если вы хотите распаковать все элементы итерации переменной длины в одну переменную, a, то вам нужно добавить запятую (,) без обозначения вторая переменная. Затем Python распакует все элементы в первую именованную переменную, которая представляет собой список.

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

Заключение

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

Вы узнали:

  • Что на самом деле означают *args и **kwargs
  • Как использовать *args и **kwargs в определениях функций
  • Как использовать одну звездочку (*) для распаковки повторяющихся объектов
  • Как использовать две звездочки (**) для распаковки словарей
Back to Top