Рубрики
Без рубрики

Аргументы переменной длины в Python с *args и **kwargs

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

Автор оригинала: Sajjad Heydari.

Вступление

Некоторые функции не имеют аргументов, другие имеют несколько. Бывают случаи, когда у нас есть функции с аргументами, о которых мы заранее не знаем. У нас может быть переменное количество аргументов, потому что мы хотим предложить гибкий API другим разработчикам или мы не знаем размер входных данных. С помощью Python мы можем создавать функции, которые принимают любое количество аргументов.

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

Использование многих аргументов с помощью *args

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

def my_min(num1, num2):
    if num1 < num2:
        return num1
    return num2

my_min(23, 50)
23

Он просто проверяет, меньше ли первое число, чем второе. Если это так, то возвращается первое число. В противном случае возвращается второе число.

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

def my_min(nums):
    result = nums[0]
    for num in nums:
        if num < result:
            result = num
    return result

my_min(4, 5, 6, 7, 2)
2

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

Аргументы переменной длины , сокращенно varargs,-это аргументы, которые могут принимать неопределенное количество входных данных. Когда они используются, программисту не нужно оборачивать данные в список или альтернативную последовательность.

В Python varargs определяются с помощью синтаксиса *args . Давайте переопределим нашу функцию my_min() с помощью *args :

def my_min(*args):
    result = args[0]
    for num in args:
        if num < result:
            result = num
    return result

my_min(4, 5, 6, 7, 2)
2

Примечание : args – это просто имя, вы можете назвать этот vararg чем угодно, если ему предшествует одна звездочка ( * ). Лучше всего продолжать называть его args , чтобы сделать его сразу узнаваемым.

Любой аргумент, следующий за *args , должен быть именованным аргументом – аргументом, на который ссылается его имя, а не позиция. С помощью *args вы больше не можете ссылаться на другой аргумент по его позиции.

Кроме того, вы можете иметь только on *args тип vararg в функции.

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

$ python3
>>> def arg_type_test(*args):
...     print(type(args))
...
>>> arg_type_test(1, 2)

С помощью *args мы можем принимать несколько аргументов последовательно , как это делается в my_mind() . Эти аргументы обрабатываются их позицией. Что делать, если мы хотим взять несколько аргументов, но ссылаться на них по имени? Мы рассмотрим, как это сделать, в следующем разделе.

Использование многих именованных аргументов с помощью **kwargs

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

def kwarg_type_test(**kwargs):
    print(kwargs)

kwarg_type_test(a="hi")
kwarg_type_test(roses="red", violets="blue")

Выход будет таким:

{'a': 'hi'}
{'roses': 'red', 'violets': 'blue'}

Используя словарь, **kwargs может сохранить имена аргументов, но он не сможет сохранить их позицию.

Примечание : Как и args , вы можете использовать любое другое имя, кроме kwargs . Однако наилучшая практика диктует, что вы должны последовательно использовать kwargs .

Поскольку **kwargs является словарем, вы можете перебирать их, как и любой другой, используя метод .items() :

def kwargs_iterate(**kwargs):
    for i, k in kwargs.items():
        print(i, '=', k)

kwargs_iterate(hello='world')

При запуске наша консоль покажет:

hello = world

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

Мы можем определить такую функцию:

def save_blog_post(content, author, tags=[], categories=[]):
    pass

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

def save_blog_post(content, author, **kwargs):
    if kwargs.get('tags'):
        # Save tags with post
        pass

    if kwargs.get('categories'):
        # Save categories with post
        pass

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

Объединение аргументов Varargs и ключевых слов

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

def combined_varargs(*args, **kwargs):
    print(args)
    print(kwargs)

combined_varargs(1, 2, 3, a="hi")

Если вы запустите этот фрагмент кода, вы увидите:

(1, 2, 3)
{'a': 'hi'}

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

  1. Известные позиционные аргументы
  2. *args
  3. Известные именованные аргументы
  4. **кварги

Функция со всеми типами аргументов может выглядеть следующим образом:

def super_function(num1, num2, *args, callback=None, messages=[], **kwargs):
    pass

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

До сих пор мы использовали синтаксис *args и **kwargs для определения функций. Python позволяет нам использовать тот же синтаксис при вызове функций. Посмотрим как!

Распаковка аргументов с помощью *args и **kwargs

Рассмотрим функцию add3() , которая принимает 3 числа и выводит их сумму. Мы можем создать его вот так:

def add3(num1, num2, num3):
    print("The grand total is", num1 + num2 + num3)

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

magic_nums = [32, 1, 7]

add3(magic_nums[0], magic_nums[1], magic_nums[2])

Если вы запустите этот код, то увидите:

The grand total is 40

Хотя это работает, мы можем сделать это более лаконичным с помощью синтаксиса *args :

add3(*magic_nums)

Выход – Общая сумма-40 , как и раньше.

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

Вы также можете аналогичным образом распаковать кортеж:

tuple_nums = (32, 1, 7)
add3(*tuple_nums) # The grand total is 40

Если вы хотите распаковать словарь, вы должны использовать синтаксис **kwargs .

dict_nums = {
    'num1': 32,
    'num2': 1,
    'num3': 7,
}

add3(**dict_nums) # The grand total is 40

В этом случае Python сопоставляет ключ словаря с именем аргумента и устанавливает его значение.

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

Вывод

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

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

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

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