用修改后的副本遮蔽全局变量

3 投票
3 回答
1925 浏览
提问于 2025-04-18 15:33

请注意:这不是关于如何在函数内部改变全局变量的问题。我知道怎么用 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 个回答

1

你可以利用 mock 这个库,暂时改变 procedure 中全局范围内的 PARAMETER 的值:

import mock

def modified_procedure():
    with mock.patch.dict(procedure.func_globals, PARAMETER=PARAMETER+1):
        return procedure()

在 Python 3 中,mockunittest 包的一部分,而不是一个第三方库,所以你可以这样做:

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
4

这有点小技巧,但你可以用下面的方法让事情顺利进行。

首先,像下面这样定义初始的方法:

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
1

你问的问题其实是关于动态作用域的。Python和大多数现在还在用的编程语言(像Perl和Emacs Lisp是个例外)都不支持动态作用域,而是使用词法作用域。

你可以修改procedure这个函数对象,来替换它的__globals__。不过这样做会显得很 hacky(不太正规),而且代码会变得更多。如果你能让procedure接受一个参数,那就这样做。如果它必须引用一个全局变量,暂时改变这个全局变量会是最简单、最不让人吃惊的方法。

撰写回答