Зачем использовать упакованные * args/** kwargs вместо передачи списка/dict?

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

def add(factor, *nums):
    """Add numbers and multiply by factor."""
    return sum(nums) * factor

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

def add(factor, nums):
    """Add numbers and multiply by factor.

    :type factor: int
    :type nums: list of int
    """
    return sum(nums) * factor

Есть ли преимущество использования упаковки аргументов *args для передачи списка чисел? Или есть ситуации, когда один из них более уместен?

Ответ 1

*args/**kwargs имеет свои преимущества, как правило, в тех случаях, когда вы хотите иметь возможность проходить в распакованной структуре данных, сохраняя при этом способность работать с упакованными. Python 3 print() - хороший пример.

print('hi')
print('you have', num, 'potatoes')
print(*mylist)

Сравните это с тем, как это было бы, если бы print() занял только упакованную структуру, а затем расширил ее внутри функции:

print(('hi',))
print(('you have', num, 'potatoes'))
print(mylist)

В этом случае *args/**kwargs действительно очень удобно.

Конечно, если вы ожидаете, что функция всегда будет передана несколько аргументов, содержащихся в структуре данных, как это делают sum() и str.join(), может возникнуть больше смысла оставить синтаксис *.

Ответ 2

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

В теории вы также можете принять словарь со значениями, установленными в None. Почему нет? Он накладные и ненужные. Для меня, принимая список, когда вы можете принимать varargs, добавляется накладные расходы. (как отметил один из комментариев)

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

Когда и если вам нужен список, то вы знаете, что вам нужен список!

Ah, обратите внимание, что f (* args) не совпадает с f (список): второй хочет список, первый принимает произвольное количество параметров (включая 0). Итак, давайте определим второй как необязательный аргумент:

def f(l = []): pass

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

PS: Я думаю, что это один из самых больших недостатков динамических языков: вы больше не видите интерфейс, но да! есть интерфейс!

Ответ 3

Это своего рода старый вариант, но для ответа на запросы @DBrenko о том, когда потребуются * args и ** kwargs, самый ясный пример, который я нашел, - это когда вам нужна функция, которая выполняет другую функцию как часть своего выполнения. В качестве простого примера:

import datetime

def timeFunction(function, *args, **kwargs):
    start = datetime.datetime.now()
    output = function(*args, **kwargs)
    executionTime = datetime.datetime.now() - start
    return executionTime, output 

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