skip to content

Functions and Comprehensions

Defining and calling functions plus what is built on them: parameter kinds, closures, lambdas, functools, comprehensions. One signature shows whether you know Python's evaluation model.

part ofPythonoverview, primer and where to startread it →
on this pageshow

explore

questions

99 · 6 sections

What do *args and **kwargs collect in a Python function definition?

level: juniorimportance: must knowfreq 82%
basics
~10 s

*args collects the leftover positional arguments into a tuple; **kwargs collects the leftover keyword arguments into a dict. Both types are fixed, and both are empty rather than None when nothing extra is passed.

open as a page

How do you write a Python function with an optional list parameter that starts empty each call?

level: juniorimportance: must knowfreq 72%
basics
~20 s

Give the parameter a default of None and build the list inside the body when the caller omitted it. Writing items=[] creates one list while the def statement runs, and every call that omits the argument shares that same list.

open as a page

In Python, why does appending to a list parameter change the caller's list while rebinding that parameter does not?

level: juniorimportance: must knowfreq 80%
basics
~20 s

A call binds the parameter name to the same object the caller passed; nothing is copied. Assigning to that name repoints only the function's local name, while calling a method like append changes the one shared object.

open as a page

What does the Python call f(*a, *b, **d1, **d2) pass to f?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Both iterables are flattened into one positional argument list, in source order, and both mappings are merged into the keyword arguments. Python 3.5 (PEP 448) allowed more than one star and double-star unpacking per call.

open as a page

What does a bare `*` in a Python function signature such as `def f(a, *, b)` do?

level: juniorimportance: must knowfreq 55%
basics
~10 s

It closes the positional part of the signature. Every parameter written after the bare * is keyword-only, so the caller must pass it by name: f(1, b=2) works and f(1, 2) raises TypeError.

open as a page

What is a free variable in a Python nested function, and how does a cell keep it alive?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A free variable is a name an inner function reads but does not bind, whose binding belongs to an enclosing function. CPython moves that variable out of the frame into a one-slot cell object, and the inner function holds the same cell, so the value outlives the enclosing call.

open as a page

Why does UnboundLocalError appear when a function does `x = x + 1` and x is a module-level global?

level: juniorimportance: must knowfreq 78%
basics
~20 s

Python decides scope at compile time: an assignment to x anywhere in the body makes x local for the whole function, so the read on the right-hand side hits an unset local slot instead of the module-level global.

open as a page

What is the difference between `global` and `nonlocal` in Python?

level: juniorimportance: must knowfreq 65%
basics
~20 s

global name makes assignments inside a function rebind the module-level name; nonlocal name rebinds the name in the nearest enclosing function. Without either declaration, an assignment simply creates a new local that shadows the outer one.

open as a page

Why does every lambda in [lambda: i for i in range(3)] return 2?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Each lambda captures the variable i itself, not a snapshot of its value at creation time. The lambdas are called after the comprehension has finished, when i holds its final value 2, so all three return 2.

open as a page

What is Python's LEGB rule, and in what order does it resolve a bare name?

level: juniorimportance: must knowfreq 78%
basics
~20 s

LEGB is the order Python searches namespaces for a bare name: Local, then any Enclosing function scopes, then the module's Global namespace, then the built-ins. The first namespace holding the name wins; if none does, Python raises NameError.

open as a page

Why are Python functions called first-class objects, and what does `def` create?

level: juniorimportance: must knowfreq 68%
basics
~20 s

A def statement runs at runtime, builds a function object and binds it to a name, exactly like an assignment. That object can be stored in a dict, passed as an argument or returned again, which is what first-class means.

open as a page

What does operator.itemgetter(2, 0) return when called on a sequence?

level: juniorimportance: must knowfreq 45%
basics
~10 s

It returns the two-element tuple (row[2], row[0]), in the order the indices were given. With a single index, operator.itemgetter returns that one item itself, not a one-element tuple.

open as a page

What does the key= argument of Python's sorted() do?

level: juniorimportance: must knowfreq 72%
basics
~10 s

key= takes a one-argument callable applied to every element; sorted, min and max then compare the values it returns instead of the elements themselves. The elements come back unchanged, only reordered.

open as a page

What is a Python lambda expression, and how does it differ from a def function?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A lambda expression builds an anonymous function whose body is a single expression, returned implicitly. def is a statement that names the function and can contain any block of code. Both create the same kind of function object.

open as a page

What do Python's built-in map() and filter() return, and how do you get a list?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Both return a lazy, one-shot iterator object, not a list. Nothing is computed until you iterate. Wrap the result in list() or tuple(), or feed it straight to a for loop, sum(), any() or all().

open as a page

What does functools.lru_cache do to a function, and how does functools.cache differ?

level: juniorimportance: must knowfreq 70%
basics
~20 s

functools.lru_cache stores each call's result in a dictionary keyed by the arguments, so a repeat call returns the stored value without running the body. functools.cache, added in 3.9, is lru_cache(maxsize=None): unbounded, and it never evicts.

open as a page

Why does functools.lru_cache raise TypeError when the function is called with a list?

level: middleimportance: must knowfreq 55%
basics
~20 s

The wrapper builds a dictionary key out of the call's arguments, so every argument must be hashable. A list is not, so the lookup raises TypeError before the function body ever runs. Pass a tuple, or a frozenset for set-like input.

open as a page

Why should the __lt__ you pair with functools.total_ordering return NotImplemented?

level: middleimportance: must knowfreq 35%
basics
~10 s

Returning NotImplemented for an operand type you do not handle lets Python try the other operand's reflected method and then raise a clear TypeError. Returning False instead answers a question you cannot answer, silently.

open as a page

Why would you use functools.partial instead of a lambda for a callable that must be pickled?

level: middleimportance: must knowfreq 46%
basics
~20 s

pickle stores a function by its importable qualified name, and a lambda has none, so pickling one fails. A functools.partial defines its own reduction — the wrapped callable plus the bound arguments — so it pickles whenever those pieces are themselves picklable.

open as a page

How does functools.singledispatch pick an implementation when several registered classes match the argument?

level: middleimportance: must knowfreq 45%
basics
~10 s

It takes the class of the first positional argument, walks that class's method resolution order, and uses the implementation registered for the nearest ancestor, falling back to object. Registration order is irrelevant; specificity decides.

open as a page

In the comprehension [x for row in rows for x in row], which for clause is the outer loop?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The leftmost one. Clauses read left to right in the same order you would write nested for statements, so for row in rows is the outer loop and for x in row the inner one. The result is one flat list.

open as a page

Why does a Python list comprehension's loop variable not leak into the enclosing scope?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Since Python 3, a comprehension evaluates its body in its own implicit function scope, so the name bound by its for clause is local to the comprehension and gone afterwards. Python 2 list comprehensions leaked it; generator expressions never did.

open as a page

In Python, how does `(x*x for x in nums)` differ from `[x*x for x in nums]`?

level: juniorimportance: must knowfreq 75%
basics
~10 s

The square-bracket form builds the whole list in memory immediately. The parenthesised form builds a generator object that computes each value only when asked, uses near-constant memory, and can be iterated only once.

open as a page

What are the list, dict and set comprehension forms in Python, and how do they differ?

level: juniorimportance: must knowfreq 85%
basics
~20 s

Square brackets build a list, braces with a colon per item build a dict, and braces without a colon build a set. All three share one shape: an output expression, a for clause, and an optional trailing if filter.

open as a page

In a Python comprehension, when does `if` filter items and when does it choose a value?

level: middleimportance: must knowfreq 62%
basics
~20 s

An if written after the for clause is a filter, so failing items are skipped and the result gets shorter. An if/else written before the for clause is a conditional expression choosing each emitted value, so every item still appears.

open as a page

What does Python's sys.getrecursionlimit() cap, and what happens when a call chain exceeds it?

level: juniorimportance: must knowfreq 48%
basics
~20 s

sys.getrecursionlimit() reports the ceiling on how many Python frames may be stacked at once, 1000 by default. When a call would go past it, CPython raises RecursionError instead of letting the thread run out of stack.

open as a page

Why does CPython still push a frame when a Python function returns a call in tail position?

level: middleimportance: must knowfreq 50%
basics
~20 s

CPython performs no tail-call optimization: returning a call compiles to an ordinary call, so a new frame is pushed while the caller's frame stays alive to receive the result. Depth grows with every recursive step.

open as a page

How do you rewrite a recursive tree walk as a Python `while` loop over an explicit stack?

level: juniorimportance: should knowfreq 40%
basics
~10 s

Seed a list with the root, then loop while it is non-empty: pop an item, do that node's work, and push its children. The list holds the pending work the frames used to hold.

open as a page

Why is sys.setrecursionlimit(1_000_000) not a safe way to allow much deeper recursion?

level: middleimportance: should knowfreq 40%
basics
~10 s

sys.setrecursionlimit() moves CPython's own frame counter; it does not enlarge the thread's machine stack. Push far past what that stack really holds and the interpreter can die outright instead of raising a catchable RecursionError.

open as a page

A proxy whose __getattr__ delegates to self._client raises RecursionError when deep-copied — why?

level: seniorimportance: should knowfreq 33%
basics
~20 s

Copying builds an instance without running init, so _client is absent. Probing that blank object for a hook name falls through to getattr, which reads self._client, which is also absent, so getattr calls itself forever.

open as a page