There have been a lot of suggestions to how lazy (deferred/late-bound) variables might be added to Python. Notably PEP 671 – Syntax for late-bound function argument defaults | peps.python.org and a general discussion as part of Backquotes for deferred expression, plus many more proposals and discussions. So why another proposal? Because the others have not got support.
Whilst in the same general space as these other proposals this proposal is different than the proceeding proposals in that it has no new syntax and has quite different semantics. Variables are annotated as having type Lazy
, which is generic. Writes to Lazy
s are wrapped by Lazy(lambda: <exp>)
. Reads have a call appended.
Details
There is a new type:
class Lazy[T]:
def __init__(self, expr: Callable[[], T]):
self.__exp = expr
def __call__(self) -> T:
if not hasattr(self, '_Lazy__value'):
self.__value = self.__exp()
self.__exp = None
return self.__value
Variables/arguments/attributes annotated as type Lazy
get special treatment when accessed. For example defining a switch
statement:
def switch[T](n: int, *exprs: Lazy[T]) -> T:
return exprs[n]
Note that the elements of the expression list are of type Lazy
. The proposal is that the compiler translates all reads of a Lazy
into a call on the instance. Therefore above is translated by the compiler into:
def switch[T](which: int, *exprs: Lazy[T]) -> T:
return exprs[which]()
Note the call added to the read, lst[n]()
.
Using switch
EG:
n = 1
x = switch(
0,
cbrt(n),
exp(n),
exp2(n),
expm1(n),
log1p(n),
log2(n),
log10(n),
sqrt(n),
)
Which the compiler translates into:
x = switch(
0,
Lazy(lambda: cbrt(n)),
Lazy(lambda: exp(n)),
Lazy(lambda: exp2(n)),
Lazy(lambda: expm1(n)),
Lazy(lambda: log1p(n)),
Lazy(lambda: log2(n)),
Lazy(lambda: log10(n)),
Lazy(lambda: sqrt(n)),
)
Each write is wrapped in Lazy(lambda: <expr>)