Title: [Pre-PEP] Optional Syntax for Typed Lambda Expressions
Hi everyone,
I would like to propose an optional syntax extension for Python’s lambda expressions to support parameter type annotations and generic type parameters.
Below is the draft abstract and specification. I am looking for feedback on the syntax choices and the rationale regarding modern typing workflows.
Abstract
This proposal introduces an optional syntax extension for Python’s lambda expressions to support parameter type annotations and generic type parameters.
To maintain parsing clarity and visual consistency with existing function definitions, typed parameters are required to be enclosed in parentheses, mirroring the syntax of def statements. Generic type parameters are placed in square brackets immediately after the lambda keyword, following the pattern established by PEP 695.
The legacy untyped lambda syntax remains fully unchanged and supported.
Rationale
Python’s type hinting system (PEP 484, PEP 695, etc.) has become a cornerstone of modern Python development. However, lambda expressions remain the only callable construct that cannot participate fully in this system.
Historically, extending lambda with annotations was viewed with caution to discourage overly complex anonymous functions. However, the landscape has evolved. Many functions that are logically simple (identity, wrappers, minor transformations) now require explicit annotations to satisfy strict static analysis, especially involving nested containers or generics.
Currently, developers must choose between:
- Retaining the concise
lambda(losing type safety). - Introducing a named
def(adding namespace pollution and separating definition from usage).
This proposal addresses this gap conservatively. By reusing the exact parameter syntax of def (parentheses) and PEP 695 (square brackets), it promotes consistency without altering the role of lambda as a lightweight tool.
Key benefits:
- Consistency: Lambdas can express the same type contracts as named functions.
- Tooling: Static analyzers gain reliable info for complex functional chains (e.g., Pandas, RxPY).
- Locality: Truly one-off callbacks remain co-located with their usage.
Specification
The lambda expression gains two optional components when typing features are used:
- Generic type parameters in square brackets (per PEP 695).
- A parenthesised parameter list supporting full annotations and return arrow.
Formal Syntax
lambda [type_params] (parameters) -> return_type: expression
lambda [type_params] (parameters) : expression
The [type_params] part is optional and follows PEP 695 exactly.
Examples
# Generic identity
lambda [T] (x: T) -> T: x
# Simple addition with annotations
lambda (x: int, y: int) -> int: x + y
# Single parameter without return annotation
lambda (x: str): x.upper()
# With default value
lambda (scale: float = 1.0) -> float: value * scale
# Generic with bound
lambda [T: int] (x: T, y: T) -> T: x + y
Legacy Form
The legacy untyped form remains unchanged:
lambda x, y: x + y
The parser distinguishes the forms by checking for [ or ( immediately after lambda.
Backwards Compatibility
This change is fully backwards compatible. All existing lambda expressions continue to work unchanged.
Note: The historical Python 2 form lambda (x, y): ... (tuple parameter unpacking) was removed in Python 3, so the parentheses syntax does not conflict with the new typed form.
Reference Implementation Notes
A draft modification to the PEG grammar (Grammar/python.gram) would add alternative branches to the lambdef rule:
lambdef[expr_ty]:
| 'lambda' t=[type_params] '(' params=parameters ')' '->' r=expression ':' b=expression
{ _PyAST_TypedLambda(t, params, r, b, EXTRA) }
| 'lambda' t=[type_params] '(' params=parameters ')' ':' b=expression
{ _PyAST_TypedLambda(t, params, NULL, b, EXTRA) }
| 'lambda' a=[lambda_params] ':' b=expression
{ _PyAST_Lambda(a, b, EXTRA) }
A new TypedLambda AST node (or an extension of the existing Lambda node) is required to store type_params and returns fields.
Discussion
I welcome any feedback or suggestions on this proposal. I am particularly interested in:
- Whether the syntax feels consistent with the rest of the language.
- If there are any edge cases in the grammar that I might have overlooked.
- Whether the requirement for parentheses `lambda (x: int): …` is seen as an acceptable trade-off for parsing clarity.
Thank you for your time and looking forward to the discussion!