简体   繁体   中英

Why use packed *args/**kwargs instead of passing list/dict?

If I don't know how many arguments a function will be passed, I could write the function using argument packing:

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

Alternatively, I could avoid argument packing by passing a list of numbers as the argument:

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

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

Is there an advantage to using argument packing *args over passing a list of numbers? Or are there situations where one is more appropriate?

*args / **kwargs has its advantages, generally in cases where you want to be able to pass in an unpacked data structure, while retaining the ability to work with packed ones. Python 3's print() is a good example.

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

Contrast that with what it would be like if print() only took a packed structure and then expanded it within the function:

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

In this case, *args / **kwargs comes in really handy.

Of course, if you expect the function to always be passed multiple arguments contained within a data structure, as sum() and str.join() do, it might make more sense to leave out the * syntax.

It's about the API: *args provides a better interface, as it states that the method accepts an arbitrary number of arguments AND that's it - no further assumptions. You know for sure that the method itself will not do anything else with the data structure containing the various arguments AND that no special data structure is necessary.

In theory, you could also accept a dictionary with values set to None. Why not? It's overhead and unnecessary. To me, accepting a list when you can accept varargs is adding overhead. (as one of the comments pointed out)

Furthermore, varargs are a good way to guarantee consistency and a good contract between the caller and the called function. No assumptions can be made.

When and if you need a list, then you know that you need a list!

Ah, note that f(*args) is not the same as f(list): the second wants a list, the first takes an arbitrary number of parameters (0 included). Ok, so let's define the second as an optional argument:

def f(l = []): pass

Cool, now you have two issues, because you must make sure that you don't modify the argument l: default parameter values . For what reason? Because you don't like *args. :)

PS: I think this is one of the biggest disadvantages of dynamic languages: you don't see anymore the interface, but yes! there is an interface!

This is kind of an old one, but to answer @DBrenko's queries about when *args and **kwargs would be required, the clearest example I have found is when you want a function that runs another function as part of its execution. As a simple example:

import datetime

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

timeFunction takes a function and the arguments for that function as its arguments. By using the *args, **kwargs syntax it isn't limited to specific functions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM