When should I use *args?
Question:
When writing Python 3.x functions, I’ve often stumbled upon the problem of writing code that accepts both a single value and a number of values.
Take this trivial function:
def capitalize(word):
return word.capitalize()
This function works well with a single string as input, but will fail if provided with multiple strings, or a list of strings.
We could turn the function to accept a list of strings, that way it would work if provided both with one value, or multiple. Even if I passed a single value, however, it would need to be wrapped in a list, and the return value would be a list as well, even when containing one element.
def capitalize(words):
return [word.capitalize() for word in words]
I recently read up on *args, and thought i could get around the awkwardness of having to pass a single string wrapped in a list as argument to the above function by rewriting it to be like the following:
def capitalize(*words):
return [word.capitalize() for word in words]
It seems to me that this is a legittimate use of *args
. However, I found a pyCon 2015 talk in which the speaker claims “variable positional arguments are good for removing noise and make your code readable, not to turn a single call api into an N api”
https://www.youtube.com/watch?v=WjJUPxKB164 at the 5:30 mark.
Is this a reasonable use of *args? If this is not how *args
is supposed to be used, is there an established way to handle single vs multi-parameter functions?
Answers:
*args combine the extra positional arguments into a tuple so you can handle them inside your function. This is optional. The required parameters inside your function preferbly be positional args, in that case you will get an error in your IDE when calling it without these parameters.
*args can be used as an extension, and for future development without breaking old implementations.
# required args
def test_req(arg1, arg2, arg3):
print arg1, arg2, arg3
# not required
def test_args(*args):
if args:
print args, type(args)
for arg in args:
print arg
test_req(1, 2, 3)
test_args(1, 2, 3)
Output
1 2 3
(1, 2, 3) <type 'tuple'>
1
2
3
Yes it’s fine to use *args
here.
You should use *args
when it’s easier to read than a list of argument (def foo(a,b,c,d,e...)
) and does not make harder to know which argument is used for what.
Your example is the perfect example of a good usage of *args
, all elements within args
will be processed in the exacte same way.
You should avoid it when you need to perform different compute for each argument, like:
def foo(*args):
do_one_stuff(args[1], args[2])
if args[3]:
do_another_stuff(args[3], args[4])
if args[6] != args[7]:
return args[0]
else:
return None
This example should make you feel what a function could be if you make a bad usage of *args
.
Short answer is: it depends.
In python *args
is used to pass an unknown number of arguments to a function. Quite similarly to calling printf()
in C which accepts an unknown number of arguments.
So, if you do not know beforehand how many arguments user would like to process using *args
is a solution. What I think was the intention of the speaker was not to use this feature unless we do not know the number of arguments. So if you’d like to compare two arguments you’d use:
def names_are_equal(name1, name2):
return name1 == name2
and not
def names_are_equal(*names):
return names[0] == names[1]
On the other hand if you’d like to, say, capitalize an unknown number of names you’d use:
def capitalize_names(*names):
return [name.upper() for name in names]
Note that using *
can be handy in other situations like unpacking arguments:
>>> l = [1, 2, 3, 4, 5]
>>> a, *b, c = l
>>> a
1
>>> b
[2, 3, 4]
>>> c
5
In addition to the accepted answer (which raises some good points), you also have to consider how your function is going to be used. Using os.path.join
as an example, it makes sense for this function to accept *args
because one is going to often be dealing with a set number of path elements, where each one might have its own name. For example:
dir_name = os.getcwd()
temp_folder = 'temp'
base_name = 'base_file.txt'
full_path = os.path.join(dir_name, temp_folder, base_name)
On the other hand, when you are working with indistinguishable elements that are naturally going to be grouped into a container object, it makes more sense to use args
. Consider the following usage with str.join
.
sentence = 'this is an example sentence'
words = sentence.split()
dash_separated = '-'.join(words)
*args
is the special parameter which can take 0 or more (positional) arguments as a tuple. *In Python, there are 2 kinds of arguments positional argument and keyword argument and my answer explains about **kwargs
:
For example, *args
can take 0 or more arguments as a tuple as shown below:
↓
def test(*args):
print(args)
test() # Here
test(1, 2, 3, 4) # Here
test((1, 2, 3, 4)) # Here
test(*(1, 2, 3, 4)) # Here
Output:
()
(1, 2, 3, 4)
((1, 2, 3, 4),)
(1, 2, 3, 4)
And, when printing *args
, 4 numbers are printed without parentheses and commas:
def test(*args):
print(*args) # Here
test(1, 2, 3, 4)
Output:
1 2 3 4
And, args
has tuple type:
def test(*args):
print(type(args)) # Here
test(1, 2, 3, 4)
Output:
<class 'tuple'>
But, *args
has no type:
def test(*args):
print(type(*args)) # Here
test(1, 2, 3, 4)
Output(Error):
TypeError: type() takes 1 or 3 arguments
And, normal parameters can be put before *args
as shown below:
↓ ↓
def test(num1, num2, *args):
print(num1, num2, args)
test(1, 2, 3, 4)
Output:
1 2 (3, 4)
But, **kwargs
cannot be put before *args
as shown below:
↓
def test(**kwargs, *args):
print(kwargs, args)
test(num1=1, num2=2, 3, 4)
Output(Error):
SyntaxError: invalid syntax
And, normal parameters cannot be put after *args
as shown below:
↓ ↓
def test(*args, num1, num2):
print(args, num1, num2)
test(1, 2, 3, 4)
Output(Error):
TypeError: test() missing 2 required keyword-only arguments: ‘num1’ and ‘num2’
But, if normal parameters have default values, they can be put after *args
as shown below:
↓ ↓
def test(*args, num1=100, num2=None):
print(args, num1, num2)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) 3 4
And also, **kwargs
can be put after *args
as shown below:
↓
def test(*args, **kwargs):
print(args, kwargs)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) {'num1': 3, 'num2': 4}
Actually, you can use other names for *args
and **kwargs
as shown below. *args
and **kwargs
are used conventionally:
↓ ↓
def test(*banana, **orange):
print(banana, orange)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) {'num1': 3, 'num2': 4}
When writing Python 3.x functions, I’ve often stumbled upon the problem of writing code that accepts both a single value and a number of values.
Take this trivial function:
def capitalize(word):
return word.capitalize()
This function works well with a single string as input, but will fail if provided with multiple strings, or a list of strings.
We could turn the function to accept a list of strings, that way it would work if provided both with one value, or multiple. Even if I passed a single value, however, it would need to be wrapped in a list, and the return value would be a list as well, even when containing one element.
def capitalize(words):
return [word.capitalize() for word in words]
I recently read up on *args, and thought i could get around the awkwardness of having to pass a single string wrapped in a list as argument to the above function by rewriting it to be like the following:
def capitalize(*words):
return [word.capitalize() for word in words]
It seems to me that this is a legittimate use of *args
. However, I found a pyCon 2015 talk in which the speaker claims “variable positional arguments are good for removing noise and make your code readable, not to turn a single call api into an N api”
https://www.youtube.com/watch?v=WjJUPxKB164 at the 5:30 mark.
Is this a reasonable use of *args? If this is not how *args
is supposed to be used, is there an established way to handle single vs multi-parameter functions?
*args combine the extra positional arguments into a tuple so you can handle them inside your function. This is optional. The required parameters inside your function preferbly be positional args, in that case you will get an error in your IDE when calling it without these parameters.
*args can be used as an extension, and for future development without breaking old implementations.
# required args
def test_req(arg1, arg2, arg3):
print arg1, arg2, arg3
# not required
def test_args(*args):
if args:
print args, type(args)
for arg in args:
print arg
test_req(1, 2, 3)
test_args(1, 2, 3)
Output
1 2 3
(1, 2, 3) <type 'tuple'>
1
2
3
Yes it’s fine to use *args
here.
You should use *args
when it’s easier to read than a list of argument (def foo(a,b,c,d,e...)
) and does not make harder to know which argument is used for what.
Your example is the perfect example of a good usage of *args
, all elements within args
will be processed in the exacte same way.
You should avoid it when you need to perform different compute for each argument, like:
def foo(*args):
do_one_stuff(args[1], args[2])
if args[3]:
do_another_stuff(args[3], args[4])
if args[6] != args[7]:
return args[0]
else:
return None
This example should make you feel what a function could be if you make a bad usage of *args
.
Short answer is: it depends.
In python *args
is used to pass an unknown number of arguments to a function. Quite similarly to calling printf()
in C which accepts an unknown number of arguments.
So, if you do not know beforehand how many arguments user would like to process using *args
is a solution. What I think was the intention of the speaker was not to use this feature unless we do not know the number of arguments. So if you’d like to compare two arguments you’d use:
def names_are_equal(name1, name2):
return name1 == name2
and not
def names_are_equal(*names):
return names[0] == names[1]
On the other hand if you’d like to, say, capitalize an unknown number of names you’d use:
def capitalize_names(*names):
return [name.upper() for name in names]
Note that using *
can be handy in other situations like unpacking arguments:
>>> l = [1, 2, 3, 4, 5]
>>> a, *b, c = l
>>> a
1
>>> b
[2, 3, 4]
>>> c
5
In addition to the accepted answer (which raises some good points), you also have to consider how your function is going to be used. Using os.path.join
as an example, it makes sense for this function to accept *args
because one is going to often be dealing with a set number of path elements, where each one might have its own name. For example:
dir_name = os.getcwd()
temp_folder = 'temp'
base_name = 'base_file.txt'
full_path = os.path.join(dir_name, temp_folder, base_name)
On the other hand, when you are working with indistinguishable elements that are naturally going to be grouped into a container object, it makes more sense to use args
. Consider the following usage with str.join
.
sentence = 'this is an example sentence'
words = sentence.split()
dash_separated = '-'.join(words)
*args
is the special parameter which can take 0 or more (positional) arguments as a tuple. *In Python, there are 2 kinds of arguments positional argument and keyword argument and my answer explains about **kwargs
:
For example, *args
can take 0 or more arguments as a tuple as shown below:
↓
def test(*args):
print(args)
test() # Here
test(1, 2, 3, 4) # Here
test((1, 2, 3, 4)) # Here
test(*(1, 2, 3, 4)) # Here
Output:
()
(1, 2, 3, 4)
((1, 2, 3, 4),)
(1, 2, 3, 4)
And, when printing *args
, 4 numbers are printed without parentheses and commas:
def test(*args):
print(*args) # Here
test(1, 2, 3, 4)
Output:
1 2 3 4
And, args
has tuple type:
def test(*args):
print(type(args)) # Here
test(1, 2, 3, 4)
Output:
<class 'tuple'>
But, *args
has no type:
def test(*args):
print(type(*args)) # Here
test(1, 2, 3, 4)
Output(Error):
TypeError: type() takes 1 or 3 arguments
And, normal parameters can be put before *args
as shown below:
↓ ↓
def test(num1, num2, *args):
print(num1, num2, args)
test(1, 2, 3, 4)
Output:
1 2 (3, 4)
But, **kwargs
cannot be put before *args
as shown below:
↓
def test(**kwargs, *args):
print(kwargs, args)
test(num1=1, num2=2, 3, 4)
Output(Error):
SyntaxError: invalid syntax
And, normal parameters cannot be put after *args
as shown below:
↓ ↓
def test(*args, num1, num2):
print(args, num1, num2)
test(1, 2, 3, 4)
Output(Error):
TypeError: test() missing 2 required keyword-only arguments: ‘num1’ and ‘num2’
But, if normal parameters have default values, they can be put after *args
as shown below:
↓ ↓
def test(*args, num1=100, num2=None):
print(args, num1, num2)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) 3 4
And also, **kwargs
can be put after *args
as shown below:
↓
def test(*args, **kwargs):
print(args, kwargs)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) {'num1': 3, 'num2': 4}
Actually, you can use other names for *args
and **kwargs
as shown below. *args
and **kwargs
are used conventionally:
↓ ↓
def test(*banana, **orange):
print(banana, orange)
test(1, 2, num1=3, num2=4)
Output:
(1, 2) {'num1': 3, 'num2': 4}