Python闭包和替换周围的范围

Python closures and replacing surrounding scope

我知道,当使用groovy闭包时,我可以更改闭包上的delegate,以便在闭包内进行的函数调用可以从外部定义。

我能用Python做类似的事情吗?

具体来说,如果采用以下代码:

1
2
3
4
5
6
7
8
9
10
def configure():
  build()

def wrap(function):
  def build():
    print 'build'

  function()

wrap(configure)

我想打印"build"(只对wrap()进行更改)。

一些注意事项:

我不想将函数传递到configure()中,因为configure()可能会调用大量函数。

我也不想全局定义这些函数,因为可能会有大量的函数可以由configure()调用,而且我不想污染全局名称空间。


是否是一个好的方法来做这是有争议的,但这里有一个不修改全局名称空间的解决方案。

1
2
3
4
5
6
7
8
9
10
11
def configure():
  build()

def wrap(f):
  import new
  def build():
    print 'build'

  new.function(f.func_code, locals(), f.func_name, f.func_defaults, f.func_closure)()

wrap(configure)

我在如何修改python中的本地名称空间中找到了它。


这在没有元编程的情况下是可行的。让configurebuild函数为参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def default_build():
    print"default build"

def configure(build_func=None):
    build_func = build_func or default_build
    build_func()

def wrap(func):
    def build():
        print"wrap build"

    func(build)

wrap(configure)

通过这种方式可以清楚地表明,configure函数的行为可以改变。

您也可以随意处理configure所看到的名称空间,以做一些更像我理解的groovy所做的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def build():
    print"default build"

def configure():
    build()

def wrap(func):
    def _build():
        print"wrap build"

    old_build = func.func_globals['build']
    func.func_globals['build'] = _build
    func()
    func.func_globals['build'] = old_build


如果你觉得自己很疯狂,很了不起,可以看看这篇关于动态范围界定的文章。

基本上,其思想是修改函数的字节码(使用byteplay模块),并将所有不严格本地范围的引用替换为那些不严格本地范围的引用。为了说明基本概念(在python伪代码中):

1
2
3
4
5
6
7
8
9
10
11
12
code = byteplay.extractcode(function)
newbytecode = []

for opcode, arg in code.code:
    if opcode in (NONLOCAL_CODES):
        opcode = LOCAL_EQUIVALENT

    newbytecode.append((opcode, arg))

code.code = newbytecode

return code.to_code()

这比这稍微复杂一点,但本文提供了一些很好的信息。

他还建议不要在生产中使用它。D


您需要使用global语句,以便在全局范围内定义build(),请尝试以下操作:

1
2
3
4
5
6
def wrap(function):
  global build
  def build():
    print 'build'

  function()


一种方法是在wrap中将build宣布为全球:

1
2
3
4
5
6
7
8
9
10
11
def configure():
  build()

def wrap(function):
  global build
  def build():
    print 'build'

  function()

wrap(configure)

但是,我并不推荐这样做,因为它会污染名称空间。