Python Basics — Default & Keyword Arguments
Default & Keyword Arguments
Default values let you make parameters optional. Keyword arguments let you pass values by name in any order.
Default Parameter Values
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Good morning")) # Good morning, Bob!
Default parameters must come after non-default parameters:
# ✅ Correct
def power(base, exponent=2):
return base ** exponent
# ❌ SyntaxError
# def power(base=10, exponent):
# return base ** exponent
Mutable Default Argument Trap
This is one of Python’s most infamous gotchas:
# ❌ DANGEROUS — mutable default is shared between calls
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] ← unexpected!
print(add_item("c")) # ['a', 'b', 'c'] ← keeps accumulating
The default list is created once when the function is defined, not on each call.
Fix: Use None as Default
# ✅ SAFE pattern
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['b'] ← independent calls
Keyword Arguments
Pass arguments by name (in any order):
def create_user(name, age, city):
return {"name": name, "age": age, "city": city}
# Positional
user1 = create_user("Alice", 30, "NYC")
# Keyword (any order)
user2 = create_user(city="London", name="Bob", age=25)
# Mixed (positional first, then keyword)
user3 = create_user("Charlie", city="Paris", age=35)
Positional-Only Parameters (/)
Python 3.8+ lets you force parameters to be positional-only:
def divide(a, b, /):
return a / b
print(divide(10, 3)) # ✅ 3.333...
# divide(a=10, b=3) # ❌ TypeError
Keyword-Only Parameters (*)
Parameters after * must be passed as keywords:
def connect(host, port, *, timeout=30, retries=3):
print(f"Connecting to {host}:{port}")
print(f"Timeout: {timeout}s, Retries: {retries}")
connect("localhost", 8080) # ✅
connect("localhost", 8080, timeout=60) # ✅
# connect("localhost", 8080, 60) # ❌ TypeError
Argument Order Rules
The full parameter order:
def example(pos_only, /, normal, *, kw_only):
pass
# pos_only → must be positional
# normal → positional or keyword
# kw_only → must be keyword
def full_example(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
full_example(1, 2, 3, d=4, e=5, f=6) # ✅
full_example(1, 2, c=3, d=4, e=5, f=6) # ✅
Practical Examples
Configuration Functions
def setup_logging(level="INFO", *, filename=None, format="%(message)s"):
print(f"Level: {level}")
if filename:
print(f"Logging to: {filename}")
print(f"Format: {format}")
setup_logging()
setup_logging("DEBUG", filename="app.log")
API Wrapper
def fetch_data(url, *, method="GET", headers=None, timeout=30):
if headers is None:
headers = {}
print(f"{method} {url} (timeout={timeout}s)")
return {"status": 200}
fetch_data("https://api.example.com/users")
fetch_data("https://api.example.com/users", method="POST", timeout=60)
Summary
- Default values make parameters optional:
def f(x, y=10): - Never use mutable defaults (lists, dicts, sets) — use
Noneinstead - Keyword arguments:
f(name="Alice", age=30) - Positional-only (
/):def f(x, /): - Keyword-only (
*):def f(*, x, y): - Order: positional-only → normal → keyword-only
Next up: *args & **kwargs →