Functions become more useful and easier to read when parameters have sensible defaults and can be passed by name. Python supports both patterns directly.
Default parameter values
You can give a parameter a default value that is used when no argument is provided:
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}")
greet("Ada") # Hello, Ada
greet("Ada", "Hi") # Hi, Ada
Parameters with defaults must come after parameters without defaults:
def greet(name, greeting="Hello"): # correct
...
def greet(greeting="Hello", name): # SyntaxError
...
Default values are evaluated once, when the function is defined, not each time it is called:
def add_item(item, items=[]):
items.append(item)
return items
first = add_item("a")
second = add_item("b")
print(first) # ['a', 'b'] — unexpected!
print(second) # ['a', 'b'] — same list
The [] is created once and shared across all calls. This is a common beginner mistake.
The fix is to use None as the default and create the mutable value inside the function:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
first = add_item("a")
second = add_item("b")
print(first) # ['a']
print(second) # ['b']
Keyword arguments
Arguments can be passed by name, not just by position:
def create_user(name, age, role="user"):
return {"name": name, "age": age, "role": role}
user = create_user(age=36, name="Ada")
# {"name": "Ada", "age": 36, "role": "user"}
Keyword arguments make calls more readable, especially when a function has many parameters:
connect(host="db.example.com", port=5432, username="admin", timeout=30)
This is clearer than connect("db.example.com", 5432, "admin", 30) because each value is labeled.
You can mix positional and keyword arguments, but all keyword arguments must come after positional ones:
create_user("Ada", age=36) # okay
create_user(name="Ada", 36) # SyntaxError — positional after keyword
Forcing keyword-only arguments
You can force certain parameters to be passed by keyword using * as a separator:
def create_user(name, *, age=None, role=None):
return {"name": name, "age": age, "role": role}
create_user("Ada", age=36, role="admin") # okay
create_user("Ada", 36, "admin") # TypeError — age and role must be keywords
Everything after * must be passed by keyword. This is useful when a function has optional parameters that would be unclear if passed positionally.
*args — variable positional arguments
Use *args to accept any number of positional arguments as a tuple:
def total(*args):
return sum(args)
total(1, 2, 3) # 6
total(10, 20) # 30
total() # 0
args is a regular tuple. You can iterate over it, index it, or pass it to other functions:
def log(*args):
for item in args:
print(item)
The name args is a convention. The * is what matters. You could write *values — it works the same way.
**kwargs — variable keyword arguments
Use **kwargs to accept any number of keyword arguments as a dictionary:
def build_profile(**kwargs):
return kwargs
profile = build_profile(name="Ada", age=36, city="London")
# {"name": "Ada", "age": 36, "city": "London"}
kwargs is a regular dictionary. You can iterate over it, check for keys, or pass it to other functions.
Combining parameter styles
A function can combine regular parameters, *args, keyword-only arguments, and **kwargs:
def create_user(name, *args, age=None, **kwargs):
user = {"name": name}
if age is not None:
user["age"] = age
user.update(kwargs)
return user
The order is fixed by Python’s syntax rules:
- regular positional parameters
*args- keyword-only parameters (after
*or*args) **kwargs
In practice, most functions use only one or two of these. A function that uses all four is probably doing too much.
Unpacking in function calls
You can also use * and ** when calling a function to unpack a collection into arguments:
def greet(first, last):
print(f"{first} {last}")
name = ("Ada", "Lovelace")
greet(*name) # Ada Lovelace — unpacks tuple into positional args
info = {"first": "Ada", "last": "Lovelace"}
greet(**info) # Ada Lovelace — unpacks dict into keyword args
This is useful when you have data in a collection and need to pass it to a function that expects individual arguments.
What to carry forward
- default values make parameters optional with a fallback
- never use a mutable value (list, dict, set) as a default — use
Noneinstead - keyword arguments make calls more readable
*forces keyword-only arguments*argscollects extra positional arguments into a tuple**kwargscollects extra keyword arguments into a dict*and**also unpack collections when calling functions
These patterns make functions flexible and self-documenting. The next lesson covers scope, which determines where variable names are accessible in your code.
Quick Check
One answerWhy should you avoid using a mutable value like [] as a default parameter?
Choose the best answer and use it to track your progress through the lesson.
Why that answer is correct
Mutable defaults are created once, not per call. That means state can leak from one function call to the next.