Python args and kwargs

Вы узнаете, как использовать **args и **kwargs в Python, чтобы повысить гибкость своих функций.

Узнаете:

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

ПРИМЕЧАНИЕ

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

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

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

def get_sum(a, b):
     return a + b
1
2

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

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

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

# sum_integers_list.py
def get_sum(input_integers):
    result = 0
    for x in input_integers:
        result += x
    return result

list_of_integers = [1, 2, 3]
print(get_sum(list_of_integers))
1
2
3
4
5
6
7
8
9

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

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

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

print(get_sum(1, 2, 3))
1
2
3
4
5
6
7

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

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

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

print(my_sum(1, 2, 3))
1
2
3
4
5
6
7

Функция все еще работает, даже если вы передаете итерируемый объект как целые числа вместо аргументов. Здесь важно только то, что вы используете оператор распаковки (*).

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

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

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

Теперь попробуйте сделать то же самое с кортежем:

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

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

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

Тип ошибки: объект 'tuple' не поддерживает назначение элемента Это потому, что кортеж является неизменным объектом, и его значения не могут быть изменены после присваивания. Имейте это в виду, когда вы работаете с кортежами и *args.

Упражнения

  1. Написать функцию с использованием *agrs, которая будет находить и возвращать максимальное число из переданной случайной длинны последовательности чисел.

  2. Написать функцию с использованием *agrs, которая будет находить и возвращать минимальное число из переданной случайной длинны последовательности чисел.

  3. Написать функцию с использованием *agrs, которая будет находить и возвращать среднее число из переданной случайной длинны последовательности чисел.

  4. Написать функцию с использованием *agrs, которая будет принимать случайное количество символов. Подсчитывать количество повторений отдельных символов. Возвращать результат в виде частотного словаря символов.

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

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

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

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))  # RealPythonIsGreat!
1
2
3
4
5
6
7

concatenate() будет перебирать словарь **kwargs и объединять все найденные значения:

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

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

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

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))
1
2
3
4
5
6
7

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

Фактически, если забудете использовать этот метод, то обнаружите, что перебираете ключи словаря kwargs, как в следующем примере:

def concatenate(**kwargs):
    result = ""
    for arg in kwargs:
        result += arg
    return result

print(concatenate(a="Real", b="Python", c="Is", d="Great", e="!"))  # abcde
1
2
3
4
5
6
7

Если вы не указали .values​​(), функция будет перебирать ключи словаря kwargs, возвращая неверный результат.

Упорядочивание аргументов в функции

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

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

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

Стандартные аргументы:

  • *args аргументы,
  • **kwargs аргументы

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

def my_function(a, b, c=0, *args, **kwargs):
    pass
1
2

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

def my_function(a, b, c=0, **kwargs, *args):
    pass
1
2

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

File "args_kwargs.py", line 2
    def my_function(a, b, **kwargs, *args):
                                    ^
SyntaxError: invalid syntax
1
2
3
4

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