Python White Magic:重新绑定全局变量

4188阅读 0评论2012-07-20 HyryStudio
分类:Python/Ruby

https://speakerdeck.com/u/antocuni/p/python-white-magic

本文的参考资料

扩展pdb模块时遇到的问题

在Python的pdb模块中有如下代码:

class Pdb(bdb.Bdb, cmd.Cmd):
    ...

def runcall(*args, **kwds):
    return Pdb().runcall(*args, **kwds)

def set_trace():
    Pdb().set_trace(sys._getframe().f_back)

为了方便使用Pdb类,pdb模块提供了诸如runcall()、set_trace()等便捷函数。在这些函数内部创建Pdb对象,并调用其对应的方法。现在我们需要继承Pdb类,为其提供更多的功能,于是在”pdbpp.py”模块中:

import pdb
class Pdb(pdb.Pdb):
    ...

通过继承pdb.Pdb,我们可以复用pdb模块中Pdb类所提供的代码,然而我们没有很好的办法复用pdb模块中的set_trace()等便捷函数。因为这些函数中的Pdb参照的是pdb模块中的Pdb类。为了复用这些函数,我们需要重新绑定其全局变量。假设rebind_globals()能重新绑定函数的全局变量,按么我们可以在”pdbpp.py”中如此复用便捷函数:

set_trace = rebind_globals(pdb.set_trace)

rebind_globals()将pdb.set_trace函数中所参照的全局变量改为当前模块中的全局变量。

rebind_globals()的实现

下面我们先看看rebind_globals()的完整代码。

import types
def rebind_globals(func, newglobals=None):
    if newglobals is None:
        newglobals = globals()
    newfunc = types.FunctionType(func.func_code,
                                 newglobals,
                                 func.func_name,
                                 func.func_defaults)
    return newfunc

rebind_globals()有一个可选参数newglobals,当它为None时将使用globals()所获得的全局变量字典作为函数func的新的全局变量。此外也可以直接传入一个字典给newglobals。

使用func的一些属性和新的全局变量字典创建一个新的函数对象,并返回它。下面我们用一个简单的例子演示rebind_globals()的功能。

>>> a = 10
>>> def f():
... print a
>>> f2 = rebind_globals(f, {"a":"changed"})
>>> f()
10
>>> f2()
changed

要完全理解这段代码的工作原理需要理解function对象。

function和code对象

在Python中函数也是对象,它有几个比较重要的属性:

下面我们看一个例子:

>>> def f(x=1, y=2):
...     return x+y
...
>>> f.func_defaults
(1, 2)
>>> f.func_name
'f'
>>> f.func_code
", line 1>
>>> f.func_globals is globals()
True

下图显示了函数对象、代码对象以及全局变量字典之间的关系:

图中,函数对象参照一个全局变量字典以及一个代码对象,代码对象中的字节码通过变量名在全局字典中查找其对应的值。显然只需要修改函数对象的func_globals属性,就能让函数在完全不同的全局变量环境之下运行。但是很遗憾,func_globals是只读属性,我们无法修改它。于是我们需要使用types模块中提供的FunctionType动态创建函数。

types模块

types模块中定义了Python所有的内置类型,例如FunctionType为函数类型:

>>> from types import FunctionType
>>> isinstance(f, FunctionType)
True
>>> FunctionType

>>> type(f)

通过FunctionType可以动态创建函数,查看其帮助:

>>> help(FunctionType)
Help on class function in module __builtin__:
class function(object)
| function(code, globals[, name[, argdefs[, closure]]])
|
| Create a function object from a code object and a dictionary.
| The optional name string overrides the name from the code object.
| The optional argdefs tuple specifies the default argument values.
| The optional closure tuple supplies the bindings for free variables.
...

它的参数分别为:

在rebind_globals()中我们使用FunctionType创建了一个新的函数对象,除了globals之外,其它的参数全部使用原函数的属性。这样就创建了一个新的函数对象,复用了原函数的代码对象。其效果如下图所示:

上一篇:二维散布点的等值线
下一篇:增加Spyder的编辑范围