Passing closures as the last argument is supported by many modern languages including Swift, Groovy, Scala, Ruby, Kotlin, Javascript. Supporting this in Python will greatly simplify many boilerplates and significantly improve code flexibility and readability.
Proposal
Suppose we have a function that takes a Callable as the last positional-only argument:
def foo(fn: Callable[[int, int, int], None], /, x=1):
fn(x, x*2, x*3)
Without anonymous function passing:
def bar(a, b, c):
print(a, b, c)
foo(bar, x=1)
foo(bar) # with default value
With anonymous function passing:
foo(x=1) a, b, c:
print(a, b, c)
# with default value
foo a, b, c:
print(a, b, c)
Alternatives
Note that we can also pass lambda function in this case, but the difference is that lambda only accepts a single statement or expression:
foo(lambda a, b, c: print(a, b, c), x=1)
Another way to achieve it is to use decorator, but itâs usually pretty cumbersome and tricky to define a decorator that optionally accepts parameters:
import functools
def foo(x_or_fn: int | Callable):
default_x = 1
def _foo(x, fn: Callable):
fn(x, x*2, x*3)
if isinstance(x_or_fn, Callable):
return _foo(default_x, x_or_fn)
else:
return functools.partial(_foo, x_or_fn)
Then we can call:
@foo(1)
def bar(a, b, c):
print(a, b, c)
# use default value
@foo
def bar(a, b, c):
print(a, b, c)
Use Case
One simple use case is a retry function:
def retry(fn, /, n=2, *, exception_type=Exception):
for i in range(n):
try:
fn()
break
except exception_type as e:
if i == n-1:
raise e
import sys
print(e, file=sys.stderr)
except Exception as e:
raise e
Then we can use it as follows:
retry():
do_some_stuff()
do_some_other_stuff()
retry(3):
do_some_stuff()
do_some_other_stuff()
retry(n=3, exception_type=ValueError):
do_some_stuff()
do_some_other_stuff()