Python functions are flexible: default parameter values, variable positional arguments (*args), variable keyword arguments (**kwargs), and concise lambdas. Comprehensions build lists, dicts and sets in one readable line. One feature — mutable default arguments — is the most famous Python trap of all.
*args, **kwargs and comprehensions
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. A list comprehension [expr for x in iterable if cond] builds a list concisely and fast — there are dict and set comprehensions too.
*args and **kwargs
def f(*args, **kwargs):
print(args) # a tuple of positional args
print(kwargs) # a dict of keyword args
f(1, 2, x=3) # args=(1, 2) kwargs={'x': 3}Comprehensions
squares = [n * n for n in range(5)] # [0, 1, 4, 9, 16]
evens = [n for n in range(10) if n % 2 == 0] # [0, 2, 4, 6, 8]
lengths = {w: len(w) for w in ["a", "bb"]} # {'a': 1, 'bb': 2}⚡ The edge
- *args becomes a tuple; **kwargs becomes a dict. They let a function accept any number of positional/keyword arguments — and you can unpack with f(*mylist, **mydict) at the call site.
- Never use a mutable default argument like def f(x, items=[]). The list is created once when the function is defined and shared across all calls, so it accumulates. Use items=None and create a fresh list inside.
Worked example
What is the difference between *args and **kwargs?
- *args collects any extra positional arguments into a tuple.
- **kwargs collects any extra keyword (named) arguments into a dictionary.
- Together they let a function accept arbitrary arguments; at a call you can also unpack a list/dict with * and **.
Answer: *args gathers extra positional args into a tuple; **kwargs gathers extra keyword args into a dict.
Worked example
Why is def f(x, items=[]) dangerous?
- The default list is created once, when the function is defined — not on each call.
- Every call that relies on the default shares that same list, so appends accumulate across calls.
- Fix it with items=None, then 'if items is None: items = []' inside the function.
Answer: The default list is created once and shared across calls, so it accumulates; use None and build a fresh list inside.
⚠ Watch out
- Mutable default arguments are shared across calls — the classic Python bug; default to None.
- Late binding in closures: a lambda in a loop captures the variable, not its value at creation — surprising results.
- Comprehensions are great but don't over-nest them — readability matters.