Default and flexible arguments make Python functions incredibly versatile — letting callers provide only what they need while still supporting rich, customizable behavior. Default parameters give optional inputs sensible fallbacks, while flexible arguments (*args for positional, **kwargs for keyword) allow any number of extra values. Together, they create clean, adaptable APIs that feel natural to use.
In 2026, modern Python functions combine defaults, *args, **kwargs, type hints, and clear docstrings to handle everything from simple calls to complex configurations. Here’s a complete, practical guide to using them effectively — with real patterns and best practices.
Start with defaults: they make parameters optional and give safe, sensible values when nothing is passed. Use immutable defaults (numbers, strings, None) — never mutable ones (lists, dicts) to avoid shared-state bugs.
def greet(name: str, greeting: str = "Hello", punctuation: str = "!") -> str:
"""Return a personalized greeting with optional style."""
return f"{greeting}, {name}{punctuation}"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!
print(greet("Charlie", punctuation=" :)")) # Hello, Charlie :)
print(greet("Dana", greeting="Hey", punctuation="!!")) # Hey, Dana!!
Flexible positional arguments (*args) collect any extra positional inputs into a tuple — perfect for variable-length lists like sums, products, or logging.
def sum_all(*numbers: float) -> float:
"""Sum any number of numeric values."""
return sum(numbers)
print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100
print(sum_all()) # 0 (empty tuple)
Flexible keyword arguments (**kwargs) collect any extra named inputs into a dictionary — ideal for configuration, options, or passing through to other functions.
def print_person(**info: str | int) -> None:
"""Print any key-value info about a person."""
for key, value in info.items():
print(f"{key.capitalize()}: {value}")
print_person(name="Emma", age=29, city="Austin", job="Engineer")
# Name: Emma
# Age: 29
# City: Austin
# Job: Engineer
Real-world power comes from combining them: defaults for common cases, *args/**kwargs for flexibility, and type hints/docstrings for clarity. Here’s a practical example — a configurable order processor.
from typing import List
def process_order(
items: List[float], # required positional
customer_id: int, # required positional
/, # end of positional-only
discount: float = 0.0, # optional keyword/positional
*, # start of keyword-only
tax_rate: float = 0.08,
currency: str = "USD",
**extra_options: str # catch-all for logging, notes, etc.
) -> dict:
"""
Process a shopping cart order with flexible options.
Args:
items: List of item prices
customer_id: Unique customer identifier
discount: Optional discount percentage (0.0–1.0)
tax_rate: Sales tax rate (default 8%)
currency: Currency code (default USD)
**extra_options: Extra metadata (e.g., notes, promo_code)
Returns:
Dictionary with subtotal, total, and status
"""
if discount < 0 or discount > 1:
raise ValueError("Discount must be between 0 and 1")
subtotal = sum(items)
discounted = subtotal * (1 - discount)
total = discounted * (1 + tax_rate)
result = {
"subtotal": round(subtotal, 2),
"total": round(total, 2),
"currency": currency,
"status": "success" if items else "empty"
}
if extra_options:
result["metadata"] = extra_options
return result
# Examples
print(process_order([100, 50], 12345)) # basic
print(process_order([200], 67890, discount=0.1)) # with discount
print(process_order(items=[150], customer_id=111, tax_rate=0.1, notes="VIP")) # keyword + extra
Best practices keep functions safe and intuitive. Always use type hints — they document intent and help IDEs/mypy catch errors. Write clear docstrings — describe parameters, returns, and any raises. Place defaults after required parameters, positional-only (/) before keyword-only (*) to enforce calling style. Use *args/**kwargs meaningfully — name them clearly (e.g. *extra_items, **config) and avoid overusing them. Prefer keyword-only parameters for options that should be explicit (tax_rate, currency). Pitfalls include mutable defaults (lists/dicts) — use None + check inside function. Another common mistake: positional args after keyword args in calls — Python forbids it. Modern tip: use typing.TypedDict or dataclasses for structured returns when multiple values grow beyond simple tuples.
Default and flexible arguments turn rigid functions into adaptable tools. In 2026, combine them with type hints, docstrings, and careful ordering to create functions that are easy to call, hard to misuse, and a joy to maintain. Master defaults for simplicity, *args/**kwargs for flexibility, and boundaries (/, *) for clarity — your code will feel natural and powerful.
Next time you write a function — think about what callers might want to customize or skip. Use defaults and flexible args to make it effortless. That’s the Python way.