DEV Community

guzmanojero
guzmanojero

Posted on

A Complete Guide to Function Arguments in Python

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)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

However, all positional arguments must come before keyword arguments.

✅ Valid:

greet("Alice", age=25)
Enter fullscreen mode Exit fullscreen mode

❌ Invalid:

greet(name="Alice", 25)
Enter fullscreen mode Exit fullscreen mode

This raises a SyntaxError.


4. Default Arguments

Parameters can define default values.

def connect(host, port=80):
    print(host, port)
Enter fullscreen mode Exit fullscreen mode

Now both of these calls are valid:

connect("example.com")
connect("example.com", 443)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Inside the function:

numbers == (1, 2, 3, 4)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "name": "Alice",
    "age": 25
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

This is equivalent to:

greet("Alice", 25)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Equivalent to:

greet(name="Alice", age=25)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Valid:

divide(10, 2)
Enter fullscreen mode Exit fullscreen mode

Invalid:

divide(a=10, b=2)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The parameters are divided into two groups:

  • a and b are positional-only parameters. They can only receive arguments by position.
  • c and d are 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)
Enter fullscreen mode Exit fullscreen mode

However, this is not:

func(a=1, b=2, c=3, d=4)
# TypeError
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Valid:

connect("example.com", timeout=10)
Enter fullscreen mode Exit fullscreen mode

Invalid:

connect("example.com", 10)
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

Example call:

func(
    1,
    2,
    3,
    4,
    5,
    6,
    e=7,
    f=8,
    color="red",
    size="large",
)
Enter fullscreen mode Exit fullscreen mode

Output:

a = 1
b = 2
c = 3
d = 4
args = (5, 6)
e = 7
f = 8
kwargs = {'color': 'red', 'size': 'large'}
Enter fullscreen mode Exit fullscreen mode

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)