Python's *args syntax, pronounced "star args," is the mechanism that lets a function accept any number of positional arguments without listing each one as a separate named parameter. You have already used functions that rely on *args without realizing it: the built-in print function accepts any number of values to display, and the max and min functions accept any number of values to compare. These functions would be impractical to write with fixed parameter lists because you never know in advance how many values a caller will want to print or compare. The *args pattern solves this problem elegantly and is one of the features that makes Python feel productive for data processing and scripting tasks.
The asterisk in *args is the unpacking operator, and args is the conventional name for the tuple that collects the extra arguments. You can use any valid variable name after the asterisk, such as *numbers or *items, but args is so universally recognized that deviating from it will cause every Python developer who reads your code to pause and wonder why. This article covers how *args works in function definitions, how the same asterisk syntax works differently at call sites to unpack collections, and the practical patterns that make *args an essential tool in your function-writing toolkit.
How *args collects extra positional arguments
When you place *args in a function's parameter list, Python takes any positional arguments that were not assigned to named parameters and packs them into a tuple. The function body can then iterate over that tuple, index into it, or pass it along to another function. The simplest demonstration shows that *args captures arguments that would otherwise cause a TypeError for too many arguments:
def log_all(*args):
for item in args:
print(f"LOG: {item}")
log_all("startup", "connected", "ready")The three strings are collected into a tuple named args, and the for loop prints each one with a prefix. Call log_all with no arguments and the loop simply does not run because the args tuple is empty. Call it with ten arguments and all ten are processed the same way. This uniform handling of zero through many arguments is exactly what makes print and max work without fixed parameter counts.
The args tuple supports all standard tuple operations. You can check its length with len(args), access specific elements by index like args[0], slice it like args[1:], and unpack it into variables if you know how many arguments to expect. The tuple is immutable, which means the function cannot accidentally modify the caller's data, a guarantee that makes *args safe to use in functions that pass their arguments through to other functions.
Where *args fits in the parameter list
The position of *args in a function signature follows strict rules. Required parameters come before *args, and any additional keyword-only parameters come after it. Parameters with default values cannot appear between the required parameters and *args because *args consumes all remaining positional arguments, leaving nothing for a defaulted parameter to receive positionally:
def configure(name, *args, debug=False):
print(f"Configuring {name}")
print(f"Extra options: {args}")
if debug:
print("Debug mode enabled")
configure("server", "verbose", "cached", debug=True)The string "server" fills the required name parameter. The strings "verbose" and "cached" go into the args tuple. The debug parameter is keyword-only because it appears after *args, so it must be passed as debug=True rather than as a fourth positional argument. This pattern of required parameters, then *args for extras, then keyword-only parameters for options appears in many real Python libraries because it keeps the function signature clear: required inputs are named, optional extras are collected without ceremony, and configuration flags are labeled.
Using *args to forward arguments to another function
One of the most common uses of *args is in wrapper functions that receive arguments and pass them through to another function without inspecting or modifying them. This pattern appears in decorators, subclass methods, and any situation where you want to add behavior before or after calling an existing function without changing how that function is called:
def timed(func):
def wrapper(*args):
import time
start = time.time()
result = func(*args)
print(f"Took {time.time() - start:.3f}s")
return result
return wrapperThe wrapper function accepts *args to capture any number of positional arguments, then uses func(*args) to forward them to the original function. The wrapper does not need to know what parameters func expects or how many there are; *args handles zero through many arguments transparently. This pattern is explored in depth in the article on Python decorators explained later in this learning path, but the core mechanism is the same *args syntax used for both collecting and forwarding. Understanding this forwarding pattern also requires familiarity with how to define and call Python functions, since the wrapper and the wrapped function both follow the same definition and calling conventions.
This forwarding pattern has an important property: it preserves the calling convention. If func expects two positional arguments, the wrapper also accepts two positional arguments and passes them through unchanged. The caller of the wrapper does not need to know that an intermediate function exists because the argument handling is transparent. This transparency is what makes decorators and wrappers composable, you can stack multiple wrappers around a function and each one only needs to forward *args without understanding the full parameter list.
The asterisk operator at the call site
The asterisk has a second role in Python that is the mirror image of its role in function definitions. When you use * before an iterable in a function call, Python unpacks that iterable into individual positional arguments. While *args in a definition packs arguments into a tuple, *items in a call unpacks a tuple or list into arguments:
values = [10, 20, 30]
print(*values) # Same as print(10, 20, 30)The list values is unpacked into three separate arguments for the print function. Without the asterisk, calling print(values) would print the list as a single object with square brackets, which is rarely what you want. The unpacking operator works with any iterable: lists, tuples, sets, and even generator expressions. This is a different operation from the *args in definitions, but it uses the same syntax, and understanding both roles of the asterisk is essential for reading real Python code.
Combining *args in definitions with * unpacking in calls creates a powerful pattern for building flexible data pipelines. A function that receives *args can store arguments, transform them, and pass them to another function using the * unpacking operator. The function in the middle does not need to know how many arguments are flowing through or what their types are; it only needs to treat them as a collection and forward them correctly.
When to use *args in your own functions
Use *args when your function genuinely needs to handle a variable number of inputs and listing every possible parameter name would be impractical or impossible. The built-in print function is the classic example: you cannot predict how many values a caller will want to print, so *args is the correct solution. Other good use cases include functions that aggregate values, such as a function that returns the product of all its arguments, and functions that log or record events, where each call might include a different number of details.
Do not use *args as a way to avoid thinking about your function's parameter list. If a function always needs exactly two inputs, write two named parameters. Named parameters are self-documenting, appear in IDE autocomplete, and give Python the opportunity to catch errors where the wrong number of arguments is passed. Using *args for a function that should take exactly two arguments hides the function's expectations and delays error detection from definition time to runtime, where it is harder to debug.
A practical middle ground is to accept required named parameters for the essential inputs and use *args for optional additional values. A function that validates an email address might take the address itself as a required parameter and use *args for additional validation rules that vary by use case. This keeps the primary input visible in the signature while allowing flexibility for the secondary inputs that are genuinely variable in number and meaning.
Rune AI
Key Insights
- *args in a function definition collects extra positional arguments into a tuple named args.
- The name args is a convention; any valid identifier works, but sticking with args keeps your code readable.
- Required parameters must come before *args; keyword-only parameters must come after it.
- The * unpacking operator at a call site spreads a list or tuple into individual positional arguments.
- Iterate over args with a for loop to process a variable number of inputs in a uniform way.
Frequently Asked Questions
What does *args do in a Python function definition?
Can I use a different name instead of args?
Can a function have both regular parameters and *args?
Conclusion
*args is Python's mechanism for writing functions that handle a flexible number of positional arguments. It collects extra arguments into a tuple, integrates cleanly with regular parameters and default values, and enables the unpacking pattern that spreads a collection into individual arguments at the call site. Mastering *args is the first step toward writing truly flexible Python function interfaces.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.