21

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?

DBedrenko
  • 4,871
  • 4
  • 38
  • 73
  • 2
    So that you don't *have* to pass in a structure in the first place. – Ignacio Vazquez-Abrams Nov 05 '15 at 11:07
  • 2
    Observe the calling syntax. Pick the one you favour for the task. – Karoly Horvath Nov 05 '15 at 11:07
  • 1
    @IgnacioVazquez-Abrams What's the advantage of that? It's just as easy to put brackets around the arguments. – DBedrenko Nov 05 '15 at 11:08
  • 1
    @NewWorld then do that, if you'd prefer. In this case, I think the latter is clearer, but YMMV. What's the *problem?* – jonrsharpe Nov 05 '15 at 11:09
  • 1
    @jonrsharpe The fact that argument packing exists implies it has an advantage in certain situtaions. I'm not aware of those situtaions, so I'm asking if one way is superior to the other. – DBedrenko Nov 05 '15 at 11:10
  • 2
    @NewWorld it's hard to answer in the general case - in your example, there's no benefit. When you hit a case where it helps, you'll know! For example, when you're doing something complex with inheritance, you can use `*args` and `**kwargs` to smooth out interface differences between different classes in the MRO. – jonrsharpe Nov 05 '15 at 11:11
  • 1
    Just because it has advantages in certain situations it doesn't mean that it's *superiour* in one of those approaches. – Karoly Horvath Nov 05 '15 at 11:12
  • @jonrsharpe It would be really helpful if someone could post one such case here. Then when I encounter a case in my own code, I won't miss it. I've been coding Python for 2 years and haven't yet encountered a situation where argument packing was superior. – DBedrenko Nov 05 '15 at 11:13
  • @KarolyHorvath It would be really helpful if you posted what advantages argument packing has over passing a list, and in what situations. – DBedrenko Nov 05 '15 at 11:14
  • 1
    It's a matter of opinion which is preferable and where (`pylint` calls it ["magic"](http://pylint-messages.wikidot.com/messages:w0142) and suggests it's avoided entirely...) and likely to end with a list of examples for and against using either method, and therefore not a good fit for SO's Q&A model. – jonrsharpe Nov 05 '15 at 11:15
  • `max(1, 2, 3)` is a pretty good example of where I'd prefer argument packing over `max([1, 2, 3])`. Yes, the difference is minuscule, but the fewer characters the better IMO. – deceze Nov 05 '15 at 11:15
  • @jon Right inb4 I did exactly that... ;-D – deceze Nov 05 '15 at 11:16
  • 1
    @jonrsharpe I disagree that it's a matter of opinion. Certain approaches in laying out code can be demonstrably more Pythonic than others. – DBedrenko Nov 05 '15 at 11:17
  • @NewWorld ...your disagreement is *also* a matter of opinion! There's no one *"pythonic"* (beyond what PEP-20 says). – jonrsharpe Nov 05 '15 at 11:17
  • @deceze With all respect, I don't think the argument packing feature was introduced to save typing 2 characters. – DBedrenko Nov 05 '15 at 11:17
  • @NewWorld In the end argument packing doesn't enable anything that would be impossible without it. All it is is syntax sugar for variadic functions, which is a thing in many different languages. You can make the same argument for or against virtually all languages that have it. It doesn't have a lot to do with Python in particular. Sometimes it's simply nice to have variadic functions for what really only boils down to simpler and/or more expressive syntax. – deceze Nov 05 '15 at 11:20
  • 1
    Also, calling `m(1, 2, 3, b=0)` is cleaner than `m([1, 2, 3], {'b': 0})`. – sobolevn Nov 05 '15 at 11:25
  • 1
    @NewWorld try writing a decorator, specifically one that doesn't know the number of arguments expected by the functions that it will decorate – dsh Nov 05 '15 at 11:43

3 Answers3

16

*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.

TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
3

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!

Markon
  • 4,480
  • 1
  • 27
  • 39
  • That last example is somewhat contrived, as it's typically solved with `def f(l=None): l = l or []`... – deceze Nov 05 '15 at 12:18
  • Why would you do that, when you can use *args? That's the point. I am offering a bad function signature and I must also fix it? That's boilerplate code to fix a design issue. – Markon Nov 05 '15 at 12:38
  • I think these issues are unrelated. If you want to offer a variadic function signature, `*args` is the most straight forward way to do it. If you want to offer a single-argument-as-list signature, `None` is the way to do it. You don't fix a function with the signature `f(l: list=None)` by using a completely different signature `f(*args)`. What you want your API to look like comes first, how to best implement it second. – deceze Nov 05 '15 at 12:58
  • That's what exactly my answer is about (the first few lines). It's all about the API and the interface you want to offer. Boilerplate code is not fixing the design issue. Actually, it's making it worse. – Markon Nov 05 '15 at 13:07
3

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.