A tuple behaves like an immutable list. The fact that you notate them with parentheses is perhaps confusing, but it's more or less a coincidence - a result of the fact that parentheses are used for grouping things together and reducing ambiguity otherwise.
When you call a function, you're not providing a tuple. You're providing arguments. A tuple can be an argument, but only one - it's just a variable of type tuple
.
What you can do is expand a tuple (or a list) into a a series of arguments with this notation:
tup = (2, 3)
f(*tup)
# expand the tuple (2,3) into a series of arguments 2, 3
You can do that with dictionaries as well, except with **
instead of *
:
my_dict = {"arg1": 1, "arg2": 2}
f(arg1=my_dict["arg1"], arg2=my_dict["arg2"])
f(**my_dict) # these are equivalent
On the other hand, functions can take arbitrary numbers of arguments (similar to how other languages do for printf()
calls). For example:
def g(*args):
print("I got this many arguments:", len(args))
Here, if you do type(args)
, you get tuple
, and if you do type(*args)
, you get an error. This is because, in function headers, the *
does the exact opposite: it packs the arguments that were given to the function into a single tuple, so that you can work with them. Consider the following:
g(2, 3) # 2 arguments, both integers
g((2, 3)) # 1 argument, a tuple
g(*(2, 3)) # 2 arguments, both integers
In short,
- functions are built in such a way that they take an arbitrary number of arguments
- The
*
and **
operators are able to unpack tuples/lists/dicts into arguments on one end, and pack them on the other end
- individual tuples/lists/dicts are otherwise just individual variables.