【问题标题】:How to dynamically construct a closure's namespace "pythonically"如何“以python方式”动态构造闭包的命名空间
【发布时间】:2016-03-10 20:43:10
【问题描述】:

我想动态构造闭包的封闭环境的命名空间,使闭包内的代码可以访问此动态内容。这是说明我的问题的最简单的玩具示例:

def f():
    exec("Y=7",locals())    
    def closure():
        v=eval("Y*2")
        return v  
    return closure

当我使用此代码时,会发生以下情况:

In [21]: Q = f()

In [22]: Q
Out[22]: <function __main__.closure>

In [23]: Q()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-23-51e72ed661f9> in <module>()
----> 1 Q()

<ipython-input-20-a47050c4450a> in closure()
      3
      4     def closure():
----> 5         return eval("Y*2")
      6
      7     return closure

<string> in <module>()

NameError: name 'Y' is not defined

因此,在这种情况下,使用“exec”将绑定变量“Y”添加到闭包的环境中不起作用。但是,由于嵌套函数,我也不能使用裸 exec。

我考虑使用 globals() 而不是 locals():

def f():
    exec("Y=7",globals())

    def closure():
        return eval("Y*2")

    return closure

## -- End pasted text --

In [27]: Q=f()

In [28]: Q
Out[28]: <function __main__.closure>

In [29]: Q()
Out[29]: 14

所以,这解决了我的直接问题,但是如果我让“exec”块具有可变值怎么办?例如,如果我更改 f() 为 Y 分配不同的值,我们就会遇到一个新问题:

def f(z):
    exec("Y="+str(z),globals())

    def closure():
        return eval("Y*2")

    return closure

## -- End pasted text --

In [33]: Q=f(2)

In [34]: Q
Out[34]: <function __main__.closure>

In [35]: Q()
Out[35]: 4

In [36]: G=f(4)

In [37]: G()
Out[37]: 8

In [38]: Q()
Out[38]: 8 #doh!

所以,我需要一种方法让绑定变量“Y”位于闭包/函数环境的命名空间中,而不是位于“local()”(无论是什么)或“global()”中,由于上述问题。

“exec”似乎无法在此特定上下文中执行。我需要执行闭包本身吗?这似乎奏效了。

def f(z):
    exec("Y="+str(z),locals())

    exec("def closure():\n return Y*2",locals())

    return eval("closure")

## -- End pasted text --

In [50]: G=f(3)

In [51]: G()
Out[51]: 6

In [52]: Q=f(5)

In [53]: Q()
Out[53]: 10

In [54]: G()
Out[54]: 6

In [55]: Q()
Out[55]: 10 #:-)

现在,如果我有一个更复杂的 Y 对象,比如函数?

def f(z,args):
  exec("Y= lambda " + args[0] + ","+args[1]+":"+args[0]+"*2+"+args[1]+"*3",locals())

  exec("def closure(x,y):\n return z*Y(x,y)",locals())

  return eval("closure")

## -- End pasted text --

In [64]: Q=f(2,['a','b'])

In [65]: Q(2,2)
Out[65]: 20

In [68]: G=f(3,['a','b'])

In [69]: G(2,-1)
Out[69]: 3

In [70]: Q(2,2)
Out[70]: 20

这似乎也有效。

不过,我想知道是否有更优雅的方式来动态构造闭包的环境。

我在 2.7.11 中使用 IPython

Python 2.7.11 |Anaconda 2.4.0 (64-bit)| (default, Jan 19 2016, 12:08:31) [MSC v.1500 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.

IPython 4.0.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: exec("x=2")

In [2]: x
Out[2]: 2

In [3]:

【问题讨论】:

  • 你为什么要这样做?为什么不直接将 Y 创建为字典,动态填充它,然后让嵌套函数访问它的键?
  • 这不应该有 Python 2.7 标签。您正在使用 exec() 作为函数。在 Python 2.x 中,它是一个语句。我必须推测您实际上使用的是 Python 3.x。
  • @BrenBarn 如果我有一些关于功能可能是什么的基本结构,那将起作用。但是,我正在解析一个可以包含任意函数、参数和参数的 yaml 文件。我需要解析 yaml 并构建一个由子函数组成的函数。例如:f(x,y)=g(h(y),x)
  • @kindall 不,这是 python 2.7.11
  • @BrenBarn 我有一个由其他函数组成的函数,所有这些函数都在一个 yaml 文件中指定,我无法假设知道其内容(字段及其内容类型除外)。所以,我需要先将子函数放入闭包的环境中,然后使用闭包来执行顶层函数。这样,我依靠闭包中的绑定来确保我已经执行了一个有效的函数。

标签: python python-2.7 namespaces closures


【解决方案1】:

在 Python 2 或 3 中不能以这种方式进行闭包;无论您尝试execeval 等,Python 编译器在closure 的任何周围函数范围内都看不到变量赋值的事实意味着closure 将查找Y 在全局范围内。

您可以使用dis.dis 验证这一点,字节码将使用LOAD_GLOBALs 来访问您的所有“闭包”变量。

您需要一次性exec def f(z):... 的整个代码。


后两个例子似乎有效,但这个例子:

def f(z):
    exec("Y="+str(z),locals())
    exec("def closure():\n return Y*2",locals())
    return eval("closure")

只会使用locals() 作为globalsexec - 这又是没有适当的关闭。您正在编译绑定到它们自己的全局环境的新函数实例,而不是闭包。

同样的代码可以写成更简单的方式

def f(z):
    globs = {'Y': z}
    exec('def closure():\n return Y*2', globs)
    return globs['closure']

【讨论】:

  • 为什么我的后两个例子似乎有效?好像我可以执行内部函数
  • 另外,我使用的是 python 2.7.11
  • 感谢您的澄清...所以当您说他们的“自己的”全局环境时,您的意思是“闭包”(不是真正的闭包)有自己的与模块分开的环境,对吗?我想要做的是避免命名空间冲突的可能性。
  • 是的,这样就可以了。只有当我知道只有少数时,我才会使用它。
  • 是的,我基本上使用“f(z)”作为函数工厂来实现它,它在它们自己的命名空间中吐出这些动态构造的函数(即使它们在配置 yaml 文件)。我真的很喜欢字典方法……不熟悉这种方法。再次感谢。不接受!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-13
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多