For simple cases, such as when the code is short and we don't have many variables to capture, we can create a temporary lambda and call it:
def f():
a = 1
g = (lambda a: lambda: a)(a)
a = 2
return g
The issue here is that the code can quickly become harder to read.
Alternatively, we can capture the variable as an optional argument:
def f():
a = 1
g = lambda a=a: a
a = 2
return g
The issue here is, of course, that we might not want the caller to be able to specify this parameter.
(And the code can be a little less readable, too.)
A fully general solution might be the following, except that it does not capture globals:
def bind(*args, **kwargs):
# Use '*args' so that callers aren't limited in what names they can specify
func = args[0]
include_by_default = args[1] if len(args) > 1 else None
# if include_by_default == False, variables are NOT bound by default
if include_by_default == None: include_by_default = not kwargs
fc = func.__code__
fv = fc.co_freevars
q = func.__closure__
if q:
ql = []
i = 0
for qi in q:
fvi = fv[i]
ql.append((lambda v: (lambda: v).__closure__[0])(
kwargs.get(fvi, qi.cell_contents))
if include_by_default or fvi in kwargs
else qi)
i += 1
q = q.__class__(ql)
del ql
return func.__class__(fc, func.__globals__, func.__name__, func.__defaults__, q)
The reason I do not attempt to capture globals here is that the semantics can get confusing -- if an inner function says global x; x = 1
, it certainly does want the global x
to be modified, so suppressing this change would quickly make the code very counterintuitive.
However, barring that, we would be able to simply use it as follows:
def f():
a = 1
g = bind(lambda: a)
a = 2
return g
print(f()())
And voilĂ , a
is instantly captured. And if we want to only capture some variables, we can do:
def f():
a = 1
b = 2
g = bind(lambda: a + b, b=5) # capture 'b' as 5; leave 'a' free
a = 2
b = 3
return g
print(f()())