What parameter forms are allowed in a Python lambda's parameter list?
answer
- Same grammar a def uses
- No parentheses around the list
- Defaults, *args, **kwargs all fine
- One thing missing: annotations
- Defaults evaluated once at creation
basics
~10 sEverything a def accepts except annotations: positional parameters, defaults, *args, **kwargs, keyword-only parameters after a bare *, and positional-only parameters before a /. The list is written without parentheses, and it may be empty.
solid answer
~40 sA lambda's parameter list uses the same grammar as a `def`, minus annotations. You can write plain positional parameters, defaults (`lambda x, step=1: x + step`), `*args`, `**kwargs`, keyword-only parameters after a bare `*`, and positional-only parameters before a `/` — the marker allowed in lambdas since Python 3.8. The list carries no surrounding parentheses, and `lambda: 0` with no parameters is valid. Two things are missing: parameter annotations are syntactically impossible, since the colon that would introduce one is the colon that ends the list; and tuple-unpacking parameters like `lambda (a, b): a + b` were removed in Python 3. Defaults behave exactly as in a `def` — evaluated once, when the lambda expression is evaluated, and stored on the function object — so a mutable default is shared by every call.
code
python · 9 linesnothing = lambda: 0
with_default = lambda x, step=1: x + step
variadic = lambda *args, **kwargs: (len(args), sorted(kwargs))
kw_only = lambda value, *, unit="cm": f"{value}{unit}"
pos_only = lambda w, h, /: w * h
print(nothing(), with_default(5), with_default(5, 3))
print(variadic(1, 2, mode="fast", retries=2))
print(kw_only(3), kw_only(3, unit="m"), pos_only(3, 4))go deeper
Know that a lambda is not restricted to one or two bare parameters: defaults, *args and **kwargs all work, and lambda: 0 with no parameters is valid. Write the list without parentheses.
Explain that the parameter grammar is the def grammar minus annotations, name the * and / markers and what they enforce, and state that defaults are evaluated once at creation and stored in __defaults__.
Be able to spot the consequence in review: a mutable default in a lambda is shared for the life of the function, and the resulting slow accumulation reads like a memory leak long before anyone suspects the signature.
Frame the boundary for the team: parameter-list richness is a signal that a callable has outgrown the inline form, so a lambda needing keyword-only markers or a mutable default should be a named function with a docstring and tests.
## The list is a full parameter list The common belief that a lambda takes only simple positional parameters is wrong. The grammar reuses the same parameter-list rule a `def` uses, with annotations stripped out. Everything below is valid: ```python nothing = lambda: 0 plain = lambda a, b: a + b with_default = lambda x, step=1: x + step variadic = lambda *args, **kwargs: (len(args), sorted(kwargs)) kw_only = lambda value, *, unit="cm": f"{value}{unit}" pos_only = lambda w, h, /: w * h ``` `*args` collects extra positional arguments into a tuple and `**kwargs` collects extra keyword arguments into a dict, exactly as in a `def`. A bare `*` in the list makes everything after it keyword-only, so callers must pass it by name. A `/` makes everything before it positional-only, so callers may not pass those by name — valid in a lambda since Python 3.8, when the marker was added to the language. The writing rules are small but trip people up. There are no parentheses around the list: `lambda (x): x` is a syntax error in Python 3, because parenthesised parameters used to mean tuple unpacking (`lambda (a, b): a + b`, valid in Python 2) and that feature was removed in Python 3.0. And the whole list may be empty — `lambda: 0` is a zero-argument function, useful as a factory or a deferred value, not a syntax error. ## What is missing, and why Annotations. `lambda x: int: x` cannot parse, because the colon that would introduce the annotation is the same token that terminates the parameter list; the grammar has nowhere to put it. This is not a lint rule that can be relaxed — it is structural. If a callable needs annotated parameters so a type checker can see them, that is a plain reason to write a `def`. A type checker can often still *infer* a lambda's parameter types from the surrounding call, but you cannot write them down. ## Defaults are evaluated once, at lambda-creation time This is the part with real consequences. The default expressions in a lambda's parameter list are evaluated when the `lambda` expression itself is evaluated, not on each call, and the resulting objects are stored on the function object in `__defaults__` (and `__kwdefaults__` for keyword-only ones). Every call that omits the argument gets *the same object back*. With an immutable default such as `1` or `"cm"` nobody notices. With a mutable one, the object is shared across every call for the lifetime of the function. Picture a nightly email-digest sender whose deduplication helper is written as `lambda item, seen=set(): ...` and mutates `seen`. The set is created once, when the module is imported, and is never cleared: the first night's run dedupes correctly, the second night's run silently drops items it "already saw", and the process's working set climbs run after run — by the time it is a 2.4 GB resident footprint, the symptom looks like a memory leak rather than a shared default. The fix is the same as for a `def`: default the parameter to `None` and build a fresh object inside — which in a lambda body means an expression, and at that point the honest move is a `def`. ```python bad = lambda item, seen=set(): (seen.add(item), len(seen))[1] print(bad("a"), bad("b"), bad("c")) # 1 2 3 — one shared set print(bad.__defaults__) # the one set, now holding all three ``` ## When the richer forms actually help The genuinely useful ones are defaults on an inline callback, and `*args`/`**kwargs` on a passthrough or a stub. A no-op hook written as `lambda *args, **kwargs: None` accepts any call signature, which is exactly what a default callback slot wants; a test double for a callback often takes the same shape. Keyword-only and positional-only markers are legal but rare inline, because a lambda short enough to belong inline is rarely one whose calling convention needs constraining — if it is, you probably want the name and the docstring too. ## What an interviewer is checking Nobody's offer turns on this. It comes up when a candidate says "lambdas are limited" and the interviewer wants to know *which* limits are real. The strong answer separates them cleanly: the body is genuinely limited to one expression, the parameter list is not limited at all except that it cannot be annotated, and defaults in a lambda carry the same once-only evaluation semantics — and the same mutable-default hazard — as they do in a `def`.
- Why is `lambda (a, b): a + b` a syntax error in Python 3?Parenthesised parameters meant tuple unpacking in Python 2, and that feature was removed in Python 3.0 by PEP 3113 because it hid a destructuring step in the signature and broke introspection. In Python 3 a lambda's parameter list is written bare, and unpacking is done in the body — or, more readably, by a `def` that unpacks its single argument.
- When is `lambda *args, **kwargs: None` a good thing to write?As a no-op default for a callback or hook slot: it accepts any call signature, so callers can invoke it however the protocol specifies without the default needing to know the shape. It is also a common stand-in for a callback in tests. Outside that, a signature-accepting-anything hides mistakes rather than tolerating them.
- Are defaults in a lambda evaluated per call?No — once, when the `lambda` expression itself is evaluated, and the objects are stored on the function object in `__defaults__` and `__kwdefaults__`. That matches `def` exactly, so a mutable default such as a list or a set is shared by every call that omits the argument, and mutating it accumulates state for the life of the function.
saying these in an interview costs you the question
- Says a lambda takes only simple positional parameters
- Writes parentheses around the parameter list
- Thinks a zero-parameter lambda is invalid
- Believes lambda parameters can be annotated
- Assumes defaults are re-evaluated on each call
- Claims *args or **kwargs require a def