在Python中禁用全局变量查找。
在Python中禁用全局变量查找。
简而言之,问题是:是否有办法阻止Python在当前作用域之外查找变量?
详细情况:
如果变量在当前作用域中未定义,Python会在外部作用域中查找变量定义。因此,在重构过程中疏忽大意时,以下代码容易出错:
def line(x, a, b): return a + x * b a, b = 1, 1 y1 = line(1, a, b) y2 = line(1, 2, 3)
如果我重命名了函数参数,但忘记在函数体内进行重命名,代码仍然可以运行:
def line(x, a0, b0): return a + x * b # 不会报错 a, b = 1, 1 y1 = line(1, a, b) # 正确的结果,巧合导致的 y2 = line(1, 2, 3) # 错误的结果
我知道从外部作用域中隐藏名称是不好的做法,但有时我们还是会这样做...
是否有办法阻止Python在当前作用域之外查找变量?(因此,在第二个例子中访问a或b会引发错误。)
在Python中,不能告诉Python不要在全局范围内查找名称。如果可以的话,你将无法使用模块中定义的任何其他类或函数,也无法使用从其他模块导入的对象,也无法使用内置名称。你的函数命名空间变得几乎没有任何需要的东西,唯一的出路就是将所有内容导入到本地命名空间中。对于你模块中的每个单独的函数都要这样做。
与其试图破坏全局查找,不如保持全局命名空间的干净。不要添加不需要与模块中其他作用域共享的全局变量。例如,使用main()
函数来封装实际上只是局部变量的内容。
此外,添加单元测试。没有测试的重构往往容易产生错误。
我一直认为在Python脚本中使用main()
函数是旧C语言实践的遗留物。现在从全局命名空间污染的角度来看待它,我有了不同的看法。谢谢:)
另一个重要的原因是允许程序作为模块导入。如果你的代码没有包装在一个函数中,那么在导入时它将被执行。
禁用Python中的全局变量查找是由于以下原因导致的:
- 无法访问任何导入的模块或变量(包括print函数)。
- 具有可选参数的函数存在问题。
为了解决这个问题,提出了以下解决方法:
def noglobal(f): return types.FunctionType(f.__code__, globals().copy(), f.__name__, f.__defaults__, f.__closure__)
这个方法是为每个被装饰的函数创建一个`globals()`的浅拷贝,以保持已导入的变量的可访问性。如果你先定义函数,再定义变量,那么这样做将实现能够在函数中访问导入的变量,但不能访问在代码中定义的变量。由于`copy()`方法创建的是浅拷贝,所以这种方法也很节省内存。
需要注意的是,使用这种方法,一个函数只能调用在其之前定义的函数,所以你可能需要重新排列你的代码。
下面是从's的Gist中复制的版本:
def imports(): for name, val in globals().items(): # module imports if isinstance(val, types.ModuleType): yield name, val # functions / callables if hasattr(val, '__call__'): yield name, val noglobal = lambda fn: types.FunctionType(fn.__code__, dict(imports()))
如果需要在代码的中间某个位置使用,由于`globals`已经与后续的变量混合在一起,所以“已定义的`globals()`”的方法可能不是理想的。在这种情况下,可以在所有导入的后面定义`init_globals = globals().copy()`,然后使用`noglobal = lambda f: types.FunctionType(f.__code__, init_globals, argdefs=f.__defaults__)`,这样就可以随时使用`noglobal`,而不用担心与新变量混淆。
需要注意的是,使用这种方法,一个模块中定义的函数`f`不能调用在该模块中定义的另一个函数`g`,即使它在`g`之前定义。
在Python中禁用全局变量查找的原因是为了消除全局变量的使用。为了解决这个问题,可以使用函数对象来替代全局变量。可以通过运行时创建函数对象来替代全局命名空间,并且可以使用自定义的装饰器来简化这个过程。
下面是一个示例代码:
import types def line(x, a0, b0): return a + x * b # 这里会报错 a, b = 1, 1 y1 = line(1, a, b) # 这里是正确的结果,巧合而已 line = types.FunctionType(line.__code__, {}) y1 = line(1, a, b) # 这里会报错,因为全局命名空间中的变量未定义
为了简化这个过程,可以定义一个自定义的装饰器:
import types noglobal = lambda f: types.FunctionType(f.__code__, {}, argdefs=f.__defaults__) def f(): return x x = 5 f() # 这里会失败
严格来说,并没有禁止访问全局变量,只是让函数相信全局命名空间中没有变量。实际上,这种方法也可以用来模拟静态变量,因为如果函数声明一个变量为全局变量并对其进行赋值,它将在自己的全局命名空间的沙盒中结束。
如果希望能够访问全局命名空间的一部分,那么需要将函数的全局沙盒填充为希望它看到的内容。
此外,还可以通过在主函数中使用这种方法来满足实际需求。
以上是一个关于禁用全局变量查找的原因以及解决方法的整理。