Definition of *args in Python
In Python, *args is a syntactic convention used in the definition of a def to allow that function to accept a variable number of positional arguments. The asterisk * is the unpacking operator, and args is simply a conventional name — you could technically use any other name, like *values or *elements, but the *args convention is universally adopted by the Python community.
When you use *args in a function signature, Python automatically groups all additional positional arguments into a tuple. This means you can call your function with zero, one, ten, or even a hundred arguments without modifying its definition.
If you want to master this concept and many other language fundamentals, we recommend following our complete Python course which covers advanced function mechanisms in depth.
*args collects positional arguments as a tuple. To collect named (keyword) arguments, use **kwargs (keyword arguments).
Basic syntax of *args
The syntax of *args is simple: just place an asterisk * before the parameter name in the function definition.
def my_function(*args):
print(type(args)) # <class 'tuple'>
for arg in args:
print(arg)
my_function(1, 2, 3)
# 1
# 2
# 3In this example, the three arguments 1, 2, and 3 are automatically grouped into the tuple (1, 2, 3) accessible via the variable args.
Practical examples of using *args
Sum function with a variable number of arguments
One of the most classic use cases of *args is creating a function capable of adding any number of values:
def total(*args):
result = 0
for number in args:
result += number
return result
print(total(1, 2)) # 3
print(total(10, 20, 30)) # 60
print(total(5)) # 5
print(total()) # 0Notice that the function works even without arguments: in that case, args is simply an empty tuple ().
Combining regular parameters and *args
You can perfectly combine regular parameters with *args. The regular parameters are filled first, and the remaining arguments are captured by *args:
def greet(greeting, *args):
for name in args:
print(f"{greeting}, {name}!")
greet("Hello", "Alice", "Bob", "Charlie")
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!Here, "Hello" is captured by the greeting parameter, and the three following names are grouped into args as ("Alice", "Bob", "Charlie"). Note the use of f-string to format the output.
Using *args to forward arguments to another function
A very common use of *args is argument forwarding from one function to another. This is particularly useful for creating wrapper functions or decorators:
def log(func, *args):
print(f"Calling {func.__name__} with arguments: {args}")
result = func(*args)
print(f"Result: {result}")
return result
def multiply(a, b):
return a * b
log(multiply, 4, 5)
# Calling multiply with arguments: (4, 5)
# Result: 20Observe that *args is used twice: first to collect the arguments into the tuple args, and a second time with func(*args) to unpack that tuple and pass each element as an individual argument to func.
Creating a decorator with *args and **kwargs
Decorators are one of the most powerful use cases for *args. By combining *args and **kwargs, you can create a decorator that works with any function, regardless of its signature:
import time
def measure_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@measure_time
def calculate_sum(*args):
return sum(args)
calculate_sum(1, 2, 3, 4, 5)
# calculate_sum took 0.0000 secondsUnpacking a list or tuple with *
The * operator is not only used in function definitions. You can also use it to unpack a list or a tuple when calling a function:
def display_coordinates(x, y, z):
print(f"x={x}, y={y}, z={z}")
coordinates = [10, 20, 30]
display_coordinates(*coordinates)
# x=10, y=20, z=30
# Also works with a tuple
point = (5, 15, 25)
display_coordinates(*point)
# x=5, y=15, z=25Extended unpacking in assignments
Since Python 3, the * operator can also be used in variable assignments to capture multiple elements:
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
head, *tail = "Python"
print(head) # P
print(tail) # ['y', 't', 'h', 'o', 'n']Warning: in an assignment with *, the starred variable always receives a list (not a tuple as with *args).
Parameter order in a Python function
When you combine different types of parameters, the order is strict and must be respected. Here is the order expected by Python:
| Position | Parameter type | Example |
|---|---|---|
| 1 | Regular positional parameters | a, b |
| 2 | *args | *args |
| 3 | Keyword-only parameters (after *) | key |
| 4 | **kwargs | **kwargs |
Here is an example illustrating this complete order:
def complete_function(a, b, *args, option=True, **kwargs):
print(f"a = {a}")
print(f"b = {b}")
print(f"args = {args}")
print(f"option = {option}")
print(f"kwargs = {kwargs}")
complete_function(1, 2, 3, 4, 5, option=False, name="test", value=42)
# a = 1
# b = 2
# args = (3, 4, 5)
# option = False
# kwargs = {'name': 'test', 'value': 42}Never place *args before regular positional parameters, as this would cause an error or unexpected behavior.
Differences between *args and **kwargs
It is essential to understand the distinction between *args and **kwargs, as these two mechanisms are complementary:
| Characteristic | *args | **kwargs |
|---|---|---|
| Collects | Positional arguments | Keyword arguments |
| Resulting type | tuple | dict |
| Call syntax | f(1, 2, 3) | f(a=1, b=2) |
| Unpacking | *my_tuple | **my_dict |
Both can be used together, as we saw in the decorator example. Together, they allow you to create extremely flexible functions capable of receiving any combination of arguments. The dict resulting from **kwargs is particularly useful for handling configuration options.
Best practices with *args
Here are the recommendations to follow for using *args effectively and maintainably:
- Respect the naming convention: use
*argsand not another name, unless a more descriptive name genuinely improves readability (like*numbersfor a mathematical function). - Document your function: when you use
*args, add a clear docstring explaining what types of arguments are expected and in what quantity. - Validate inputs: since
*argsaccepts everything, remember to check the type and number of received arguments if necessary. - Don't overuse *args: if your function always expects exactly two or three arguments, use explicit named parameters.
*argsshould be reserved for cases where the number of arguments is truly variable. - Prefer type annotations: since Python 3.11+, you can annotate
*argswith*args: intto indicate the expected type of each argument.
def average(*args: float) -> float:
"""Calculate the average of a variable number of values.
Args:
*args: The numeric values for which to
calculate the average.
Returns:
The arithmetic mean of the values.
Raises:
ValueError: If no value is provided.
"""
if not args:
raise ValueError("At least one value is required")
return sum(args) / len(args)
print(average(10, 20, 30)) # 20.0Note the use of the len function to get the number of elements in the args tuple.
Common use cases for *args
Here are the most frequent situations where *args proves indispensable:
- Decorators: to create generic decorators that work with any function.
- Mathematical functions: sum, product, average, minimum, maximum on a variable number of values.
- class inheritance: to forward arguments to the parent class constructor with
super().__init__(*args). - Wrappers and proxies: to wrap a function call while faithfully forwarding its arguments.
- Display functions: Python's print function itself uses
*argsto accept a variable number of objects to display. - Collection concatenation: merging multiple lists, tuples, or set passed as arguments.
Common mistakes to avoid
Here are the most frequent pitfalls related to using *args:
Forgetting the asterisk when unpacking
def display(a, b, c):
print(a, b, c)
values = (1, 2, 3)
# Error: passes the entire tuple as the first argument
# display(values) # TypeError
# Correct: unpack the tuple
display(*values) # 1 2 3Confusing *args (tuple) with a list
def modify(*args):
# args is a tuple, therefore immutable
# args[0] = 10 # TypeError: 'tuple' does not support item assignment
# Convert to list if modification is needed
args_list = list(args)
args_list[0] = 10
return args_list
print(modify(1, 2, 3)) # [10, 2, 3]Placing *args after **kwargs
# SYNTAX ERROR:
# def wrong(**kwargs, *args): # SyntaxError
# pass
# CORRECT:
def correct(*args, **kwargs):
passFrequently asked questions
Is the name "args" mandatory in Python?
No, the name args is not mandatory. It is the asterisk * that provides the positional argument collection behavior. You could write *numbers, *elements, or any other valid name. However, *args is a very strong convention in the Python community (defined in PEP 8), and following it makes your code immediately understandable by other developers.
Can you use *args with type annotations?
Yes, since Python 3.11 (PEP 646), you can annotate *args directly. For example, def my_function(*args: int) indicates that all collected arguments should be of type int. Before Python 3.11, you could use typing.Tuple or typing.Any for more complex annotations. These annotations have no effect at runtime, but they are used by static analysis tools like mypy.
What is the difference between *args in a definition and in a function call?
In a function definition (def f(*args)), the * operator collects (packs) positional arguments into a tuple. In a function call (f(*my_list)), the * operator unpacks an iterable into individual arguments. These are two inverse operations that use the same syntax.
How can I learn to master *args and advanced functions in Python?
To deepen your understanding of *args, **kwargs, decorators, and all the subtleties of functions in Python, we recommend following our Python course on Believemy. You will find practical exercises, real-world projects, and comprehensive pedagogical support to master these essential Python development concepts.