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 *
def rebind_globals(func, newglobals=None):
if newglobals is None:
newglobals = globals() ❶
newfunc = *.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中函数也是对象,它有几个比较重要的属性:
func_globals: 函数所参照的全局变量字典,通常就是定义函数时通过globals()获得的字典。
func_code: 表示函数字节码的代码对象。
func_name: 定义函数时的函数名。
func_defaults: 保持函数缺省值的元组。
下面我们看一个例子:
>>> 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是只读属性,我们无法修改它。于是我们需要使用*模块中提供的FunctionType动态创建函数。
*模块
*模块中定义了Python所有的内置类型,例如FunctionType为函数类型:
>>> from * 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.
...
它的参数分别为:
code: 代码对象
globals: 全局变量字典
name: 函数名
argdefs: 缺省值
closure: 这个属性只在嵌套函数中有用
在rebind_globals()中我们使用FunctionType创建了一个新的函数对象,除了globals之外,其它的参数全部使用原函数的属性。这样就创建了一个新的函数对象,复用了原函数的代码对象。其效果如下图所示: