如何在Python中不可避免地捕获闭包变量的CURRENT值?

时间:2017-05-29 10:42:03

标签: python lambda closures

如果我这样做

var xor = newF ^ oldF;
var added = newF & xor;
var removed = oldF & xor;

打印的值为def f(): a = 1 g = lambda: a a = 2 return g print(f()()) ,因为构建2a会发生变异。
如何让g静态捕获g的值,以便以后的修改被忽略?

2 个答案:

答案 0 :(得分:1)

对于简单的情况,例如当代码很短并且我们没有很多变量要捕获时,我们可以创建一个临时的lambda并调用它:

a

这里的问题是代码很快就会变得难以阅读。

或者,我们可以将变量捕获为可选参数:

def f():
    a = 1
    g = (lambda a: lambda: a)(a)
    a = 2
    return g

问题 here 当然是我们可能不希望调用者能够指定此参数。
(而且代码的可读性也差一点。)

完全通用解决方案可能如下,但不捕获全局

def f():
    a = 1
    g = lambda a=a: a
    a = 2
    return g

我不试图在这里捕获全局变量的原因是语义可能会让人感到困惑 - 如果内部函数说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) ,那么确实 希望全局< / em> global x; x = 1要修改,因此抑制此更改会很快使代码违反直觉。

但是,除此之外,我们可以简单地按如下方式使用它:

x

瞧,def f(): a = 1 g = bind(lambda: a) a = 2 return g print(f()()) 即刻被捕获。如果我们只想捕获一些变量,我们可以这样做:

a

答案 1 :(得分:1)

在python3.8中,CellType类已添加到types中,这意味着您可以使用自定义闭包创建函数。这使我们能够编写一个函数,该函数将带有引用父框架的闭包的函数转换为具有静态值的闭包:

from types import FunctionType, CellType

def capture(f):
    """ Returns a copy of the given function with its closure values and globals shallow-copied """
    closure = tuple(CellType(cell.cell_contents) for cell in f.__closure__)
    return FunctionType(f.__code__, f.__globals__.copy(), f.__name__, f.__defaults__, closure)

print([f() for f in [        lambda: i  for i in range(5)]]) # Outputs [4, 4, 4, 4, 4]
print([f() for f in [capture(lambda: i) for i in range(5)]]) # Outputs [0, 1, 2, 3, 4]

capture函数可以通过几种方式进行调整;当前的实现捕获所有全局变量和自由变量,并且浅层捕获。您可以决定对捕获的值进行深度复制,仅捕获自由变量,仅根据参数捕获特定的变量名称,等等。