One of the biggest reasons I don’t use lambda functions is that they are difficult to debug. They would be a bit easier to debug if they had a better repr() dependent on their origin in the code. For example, I think a lambda should have specialized __qualname__ (and sometimes __name__) in the following circumstances:
# assignment to a variable
func = lambda: ...
# named "func"
# assignment to expression
my_object.funcs['my_lambda'] = lambda: ...
# qualname is "my_object.funcs['my_lambda']"
# literal container
operators = {
'add': lambda x, y: x+y
}
# __qualname__ is operators['add']
In general, if the qualname ends with a python identifier, __name__ should be set to that identifier, otherwise it should probably stay as <lambda>.
For lambdas used as an argument to a function, we could do something like this to indicate what the function is used for
# keyword arg
sorted(..., key=lambda: ...)
# qualname is key@sorted
# positional arg
filter(lambda: ..., ...)
# qualname is <lambda>@filter
If we want to take the idea even further, we can generate a repr() dynamically based on values of closure variables. For example
I think this is likely to be significantly harder than you imagine, because the lambda (function) object does not know what variable(s) it has been assigned to. I’m not even sure there’s a meaningful answer to “what is the name of this lambda”, given that objects can be assigned to multiple names at the same time.
How would you handle something like
f = lambda x: x + 1
g = f
# Should the lambda be named f or g?
del f
# What would the lambda's __name__ be now?
I suggest that if you want to take this forward, you look at the interpreter source and work out how you would implement it. If you can find a reasonable implementation strategy, then it may be worth taking further.
Or maybe, as a less ambitious proposal, the repr of a lambda could include the source file and line it was defined on? That information is currently available on the __code__ attribute of the lambda object.
Something like the following will “patch up” a lambda’s qualname, if you want to try it out:
because the lambda (function) object does not know what variable(s) it has been assigned to
It would be done at compile time, while parsing the abstract syntax tree.
# Should the lambda be named f or g?
It would still be named f. It’s about what it’s assigned to initially.
If you can find a reasonable implementation strategy, then it may be worth taking further.
Here’s a pure-python dummy implementation using ast.NodeTransformer. This works for assignments, but could be extended to the other cases as well.
type AnyAssign = ast.Assign | ast.AnnAssign | ast.NamedExpr
# example regex to parse __qualname__ and __name__ from an expression
# i.e. foo.bar -> ('foo.bar', 'bar')
good_name = re.compile(r"^(?:(?:[a-zA-Z0-9\[\]\.]+\.)?([a-zA-Z0-9]+)|[a-zA-Z0-9\[\]\.]+)$")
def make_lambda[T](func: T, qualname: str|None, name: str|None) -> T:
"""
Runtime magic to rename a lambda function.
In the real implementation, the name and qualname
would be stored in the bytecode directly.
"""
if not qualname:
qualname = '<lambda>'
if not name:
name = '<lambda>'
func.__name__ = name
func.__qualname__ = qualname
return func
class Transformer(ast.NodeTransformer):
"""Finds lambda expressions in assignments
and renames them to the assignment target"""
def visit_Assign(self, node):
return self.maybe_lambda(node)
def visit_AnnAssign(self, node):
return self.maybe_lambda(node)
def get_name(self, node: AnyAssign) -> tuple[str|None, str|None]:
"""Determine best candidates for __qualname__, __name__
from an assignment node"""
name = None
if hasattr(node, 'target'):
name = good_name.match(ast.unparse(node.target))
if not name and hasattr(node, 'targets'):
for n in node.targets:
maybe = good_name.match(ast.unparse(n))only
if maybe:
name = maybe
if maybe.group(1):
# __name__ candidate is a valid identifier.
break
return (
name.group(0) if name else None,
name.group(1) if name else None
)
def maybe_lambda(self, node: AnyAssign):
"""If the node's value is a lambda, rename it to the node's target."""
lam = node.value
if isinstance(lam, ast.Lambda):
name, qualname = self.get_name(node)
# Use runtime magic to rename the lambda.
node.value = ast.Call(
func=ast.Name(id='make_lambda', ctx=ast.Load()),
args=[
lam,
ast.Constant(value=name),
ast.Constant(value=qualname)],
keywords=[]
)
self.generic_visit(node)
return node
I second this. PEP-008 recommends against this usage. One reason people use an anonymous expression to define a function is to avoid thinking of a name. If you name the function, use def to assign the name internally.
Right. Lambda expressions are intended for return expressions that are simple enough to not need debugging. They should never raise.
So on second thought, since I really only want this feature in interactive code (I never use lambdas in application code), I am going to write an IPython extension that implements custom naming and pretty-printing for lambdas.