用修改后的副本遮蔽全局变量
请注意:这不是关于如何在函数内部改变全局变量的问题。我知道怎么用 global
关键字。
我的脚本里有一堆全局配置变量。我想写一个函数(下面叫 modified_procedure()
),它可以在本地命名空间中“遮盖”其中一个全局变量,并调用另一个函数,这个函数会用到这个配置变量。也就是说,
PARAMETER = 1
def procedure():
return PARAMETER * 3
def modified_procedure():
PARAMETER += 1
return procedure()
这样做会失败,因为 PARAMETER
出现在 modified_procedure()
的函数体内,所以解释器把它当作一个局部变量,而不会去全局命名空间里查找。我并不是想改变全局变量 PARAMETER
;我只是想在 modified_procedure()
的命名空间中遮盖它。
我能想到几个不太方便的解决办法:
- 把脚本改成面向对象的方式,这样配置变量就变成对象的属性,然后我可以创建一个子类,里面有一个新的
procedure()
。 - 在
modified_procedure()
中使用global
来修改PARAMETER
,然后在返回procedure()
的结果之前把它恢复。
我能通过遮盖 PARAMETER
来做到这一点吗?如果可以,怎么做呢?
3 个回答
你可以利用 mock
这个库,暂时改变 procedure
中全局范围内的 PARAMETER
的值:
import mock
def modified_procedure():
with mock.patch.dict(procedure.func_globals, PARAMETER=PARAMETER+1):
return procedure()
在 Python 3 中,mock
是 unittest
包的一部分,而不是一个第三方库,所以你可以这样做:
from unittest import mock
def modified_procedure():
with mock.patch.dict(procedure.__globals__, PARAMETER=PARAMETER+1):
return procedure()
如果不使用 mock
,你可以尝试类似下面的做法:
def modified_procedure():
# Support Python 2 and 3.
try:
d = procedure.__globals__
except AttributeError:
d = procedure.func_globals
# Ensure that procedure()'s globals are restored
# even if it raises an exception.
try:
d['PARAMETER'] += 1
return procedure()
finally:
d['PARAMETER' -= 1
这有点小技巧,但你可以用下面的方法让事情顺利进行。
首先,像下面这样定义初始的方法:
PARAMETER = 1
def procedure(PARAMETER = PARAMETER):
return PARAMETER * 3
接下来,在第二个方法中,你可以使用 globals()
方法,并用修改过的 PARAMETER
来调用它:
def modified_procedure_v1():
PARAMETER = globals()["PARAMETER"] + 1
return procedure()
你甚至可以在修改过的方法中使用更新后的 PARAMETER 值来调用第一个方法,方法如下:
def modified_procedure_v2():
PARAMETER = globals()["PARAMETER"] + 1
return procedure(PARAMETER)
最终结果就变成了:
>>> modified_procedure_v1() #uses global PARAMETER
3
>>> modified_procedure_v2() #uses PARAMETER value local to this method
6
你问的问题其实是关于动态作用域的。Python和大多数现在还在用的编程语言(像Perl和Emacs Lisp是个例外)都不支持动态作用域,而是使用词法作用域。
你可以修改procedure
这个函数对象,来替换它的__globals__
。不过这样做会显得很 hacky(不太正规),而且代码会变得更多。如果你能让procedure
接受一个参数,那就这样做。如果它必须引用一个全局变量,暂时改变这个全局变量会是最简单、最不让人吃惊的方法。