Python

Functions And Functional Basics

Function Arguments

Give a parameter a fallback value, making it optional at call time:

JrCodex·5 min read

Jr Codex Python Notes

Level: Intermediate Prerequisites: Chapter 1 Time to complete: ~25 minutes


Table of Contents

  1. Default Arguments
  2. The Mutable Default Argument Trap
  3. *args — Variable Positional Arguments
  4. **kwargs — Variable Keyword Arguments
  5. Combining Everything
  6. Keyword-Only & Positional-Only Arguments
  7. Summary & Next Steps

1. Default Arguments

Give a parameter a fallback value, making it optional at call time:

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"
 
print(greet("Alice"))                 # "Hello, Alice!" — uses default
print(greet("Bob", "Hi"))              # "Hi, Bob!" — overrides default
print(greet("Charlie", greeting="Hey")) # "Hey, Charlie!" — keyword form

Rule: parameters with defaults must come after parameters without defaults:

def broken(name="Anonymous", age):    # SyntaxError: non-default argument follows default argument
    pass
 
def fixed(age, name="Anonymous"):      # ✓ correct order
    pass

2. The Mutable Default Argument Trap

One of Python's most infamous gotchas — a default value is evaluated once, when the function is defined, not each time it's called.

def add_item(item, cart=[]):          # ✗ DANGER: mutable default
    cart.append(item)
    return cart
 
print(add_item("apple"))        # ['apple']
print(add_item("banana"))        # ['apple', 'banana'] — NOT a fresh list! Same object reused.
Why This Happens
─────────────────────────────────────────
  def add_item(item, cart=[]):
                        ↑
        This list is created ONCE, at function definition time,
        and reused across EVERY call that doesn't pass its own cart.
─────────────────────────────────────────

The fix: use None as the default, and create the mutable object inside the function body:

def add_item(item, cart=None):        # ✓ safe
    if cart is None:
        cart = []
    cart.append(item)
    return cart
 
print(add_item("apple"))     # ['apple']
print(add_item("banana"))     # ['banana'] — fresh list each time

This is one of the most common real-world Python bugs — internalize the fix.


3. *args — Variable Positional Arguments

Use *args to accept any number of positional arguments — they arrive packed into a tuple.

def total(*args):
    print(type(args))     # <class 'tuple'>
    return sum(args)
 
print(total(1, 2, 3))         # 6
print(total(1, 2, 3, 4, 5))    # 15
print(total())                   # 0 — works with zero args too

args is just a conventional name — the * is what matters:

def show(*numbers):
    for n in numbers:
        print(n)
 
show(10, 20, 30)

4. **kwargs — Variable Keyword Arguments

Use **kwargs to accept any number of keyword arguments — they arrive packed into a dictionary.

def build_profile(**kwargs):
    print(type(kwargs))     # <class 'dict'>
    return kwargs
 
profile = build_profile(name="Alice", age=25, city="Boston")
print(profile)                 # {'name': 'Alice', 'age': 25, 'city': 'Boston'}
 
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")
 
print_info(name="Bob", role="Engineer")

5. Combining Everything

Python enforces a strict order when mixing parameter types:

def full_example(a, b, *args, c=10, **kwargs):
    print("a, b:", a, b)
    print("args:", args)
    print("c:", c)
    print("kwargs:", kwargs)
 
full_example(1, 2, 3, 4, 5, c=99, x=100, y=200)
# a, b: 1 2
# args: (3, 4, 5)
# c: 99
# kwargs: {'x': 100, 'y': 200}
Required Parameter Order
─────────────────────────────────────────
  def f(positional, *args, keyword_default=value, **kwargs):
         ↑              ↑            ↑                ↑
     required        extra       optional          extra
     positional    positional    keyword-only      keyword
─────────────────────────────────────────

The * and ** Also Unpack at the Call Site

def add(a, b, c):
    return a + b + c
 
numbers = [1, 2, 3]
print(add(*numbers))          # unpacks list into 3 positional args → 6
 
values = {"a": 1, "b": 2, "c": 3}
print(add(**values))            # unpacks dict into keyword args → 6

This "unpacking at the call site" pattern shows up constantly — passing a config dict straight into a function, forwarding args through a wrapper (foundational for decorators, Module 5).


6. Keyword-Only & Positional-Only Arguments

Python lets you force certain arguments to be passed only by keyword (or only by position) — useful for API clarity.

# Keyword-only: anything after a bare * must be passed by keyword
def create_user(name, *, age, role="user"):
    return f"{name}, {age}, {role}"
 
create_user("Alice", age=25)             # ✓ works
create_user("Alice", 25)                  # ✗ TypeError — age must be a keyword arg
 
# Positional-only: anything before / must be passed positionally (Python 3.8+)
def divide(a, b, /):
    return a / b
 
divide(10, 2)             # ✓ works
divide(a=10, b=2)          # ✗ TypeError — a and b must be positional
SyntaxEffect
def f(a, *, b):a positional or keyword; b keyword-only
def f(a, /, b):a positional-only; b positional or keyword

This mostly matters for library/API design — forcing keyword args (like age= above) makes call sites self-documenting and prevents accidental argument-order bugs.


7. Summary & Next Steps

Key Takeaways

  • Default arguments make parameters optional — but never use a mutable object (list, dict) as a default; use None and initialize inside the function.
  • *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict.
  • The same */** syntax unpacks collections into arguments at the call site — add(*[1,2,3]).
  • * alone in a signature forces everything after it to be keyword-only; / forces everything before it to be positional-only.

Concept Check

  1. Why is def add_item(item, cart=[]): dangerous, and what's the fix?
  2. What data structure does *args produce inside the function? What about **kwargs?
  3. Given def f(a, *, b):, is f(1, 2) valid? Why or why not?

Next Chapter

Chapter 3: Scope & Closures


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index