Every Way to Pass Arguments to a Function in Python
One of Python's strengths is the flexibility of its function signatures. Most developers learn positional and keyword arguments early on, but Python supports several other ways to define and pass arguments that make APIs more expressive and easier to evolve.
In this article, we'll cover every way arguments can be passed to a function in Python, from the basics to more advanced features like positional-only and keyword-only parameters.
1. Positional Arguments
The most common way to call a function is by passing arguments in the order the parameters are defined.
def greet(name, age):
print(f"{name} is {age} years old.")
greet("Alice", 25)
Python matches arguments to parameters by their position.
2. Keyword Arguments
Instead of relying on order, you can specify the parameter names explicitly.
greet(age=25, name="Alice")
Since the names are provided, the order no longer matters.
This often makes function calls more readable, especially when there are several parameters.
3. Mixing Positional and Keyword Arguments
You can combine both styles in the same function call.
greet("Alice", age=25)
However, all positional arguments must come before keyword arguments.
✅ Valid:
greet("Alice", age=25)
❌ Invalid:
greet(name="Alice", 25)
This raises a SyntaxError.
4. Default Arguments
Parameters can define default values.
def connect(host, port=80):
print(host, port)
Now both of these calls are valid:
connect("example.com")
connect("example.com", 443)
Default arguments help keep common function calls concise while still allowing customization when needed.
5. Variable Positional Arguments (*args)
Sometimes you don't know how many positional arguments will be passed.
*args collects them into a tuple.
def add(*numbers):
print(numbers)
return sum(numbers)
print(add(1, 2, 3, 4))
Inside the function:
numbers == (1, 2, 3, 4)
This is commonly used for functions that accept an arbitrary number of values.
6. Variable Keyword Arguments (**kwargs)
Similarly, **kwargs collects extra keyword arguments into a dictionary.
def create_user(**kwargs):
print(kwargs)
create_user(name="Alice", age=25)
Output:
{
"name": "Alice",
"age": 25
}
This pattern is useful for flexible APIs or forwarding arguments to another function.
7. Unpacking Iterables with *
The * operator can also be used when calling a function.
Instead of collecting arguments, it expands an iterable into positional arguments.
values = ["Alice", 25]
greet(*values)
This is equivalent to:
greet("Alice", 25)
It works with lists, tuples, generators, and any iterable.
8. Unpacking Dictionaries with **
Likewise, ** expands a dictionary into keyword arguments.
person = {
"name": "Alice",
"age": 25,
}
greet(**person)
Equivalent to:
greet(name="Alice", age=25)
This is especially convenient when working with configuration dictionaries or serialized data.
9. Positional-Only Parameters (/)
Introduced in Python 3.8, the / marker allows you to define parameters that cannot be passed by keyword.
def divide(a, b, /):
return a / b
Valid:
divide(10, 2)
Invalid:
divide(a=10, b=2)
This raises a TypeError.
Why would you use this?
Positional-only parameters let you change parameter names in future versions without breaking users' code, since callers never reference those names.
The Python standard library uses this technique extensively.
Understanding the / Separator
It's important to understand that the / does not separate positional arguments from keyword arguments. Instead, it classifies the parameters in the function definition.
Consider the following example:
def func(a, b, /, c, d):
pass
The parameters are divided into two groups:
-
aandbare positional-only parameters. They can only receive arguments by position. -
canddare positional-or-keyword parameters. They can receive arguments either by position or by name.
For example, both of these calls are valid:
func(1, 2, 3, 4)
func(1, 2, c=3, d=4)
However, this is not:
func(a=1, b=2, c=3, d=4)
# TypeError
A common misconception is that every parameter after / must be passed by keyword. That's not the case, they can still be passed positionally unless a * later in the signature makes them keyword-only.
10. Keyword-Only Parameters (*)
The opposite is also possible.
Parameters after * must be passed by keyword.
def connect(host, *, timeout):
print(host, timeout)
Valid:
connect("example.com", timeout=10)
Invalid:
connect("example.com", 10)
This raises a TypeError.
Keyword-only parameters improve readability and help prevent mistakes, particularly when a function has several optional arguments.
Putting It All Together
A single function can combine almost every parameter type.
def func(
a,
b,
/,
c,
d=0,
*args,
e,
f=10,
**kwargs,
):
print(f"a = {a}")
print(f"b = {b}")
print(f"c = {c}")
print(f"d = {d}")
print(f"args = {args}")
print(f"e = {e}")
print(f"f = {f}")
print(f"kwargs = {kwargs}")
Example call:
func(
1,
2,
3,
4,
5,
6,
e=7,
f=8,
color="red",
size="large",
)
Output:
a = 1
b = 2
c = 3
d = 4
args = (5, 6)
e = 7
f = 8
kwargs = {'color': 'red', 'size': 'large'}
The Five Kinds of Parameters
Although we've discussed many ways to pass arguments, Python actually recognizes five kinds of parameters when defining a function.
| Parameter Kind | Positional | Keyword |
|---|---|---|
| Positional-only | ✅ | ❌ |
| Positional-or-keyword | ✅ | ✅ |
Variadic positional (*args) |
Collects extras | ❌ |
| Keyword-only | ❌ | ✅ |
Variadic keyword (**kwargs) |
❌ | Collects extras |
Everything else, default values and argument unpacking, is built on top of these parameter kinds.
Summary
| Method | Example | Description |
|---|---|---|
| Positional arguments | f(1, 2) |
Matched by position. |
| Keyword arguments | f(x=1, y=2) |
Matched by parameter name. |
| Mixed positional + keyword | f(1, y=2) |
Positional first, keywords after. |
| Default arguments | def f(x, y=0) |
Caller may omit the value. |
Arbitrary positional (*args) |
def f(*args) |
Collects extra positional arguments into a tuple. |
Arbitrary keyword (**kwargs) |
def f(**kwargs) |
Collects extra keyword arguments into a dictionary. |
Iterable unpacking (*) |
f(*values) |
Expands an iterable into positional arguments. |
Dictionary unpacking (**) |
f(**options) |
Expands a dictionary into keyword arguments. |
Positional-only parameters (/) |
def f(x, /) |
Caller cannot use x=. |
Keyword-only parameters (*) |
def f(*, x) |
Caller must use x=. |
Final Thoughts
Python's function signature syntax is remarkably expressive. Whether you're designing a simple helper function or a public API, choosing the right parameter types can make your code:
- More readable
- Easier to maintain
- Safer to evolve
- Less error-prone
If you've only been using positional and keyword arguments, learning about positional-only and keyword-only parameters can significantly improve the clarity and stability of your APIs.
If you didn't know this, now you know it 💪💪💪
Top comments (0)