在Python中禁用全局变量查找

2024-04-26 00:56:03 发布

您现在位置: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  # not an error

a, b = 1, 1
y1 = line(1, a, b)  # correct result by coincidence
y2 = line(1, 2, 3)  # wrong result

我知道it is bad practice to shadow names from外部范围。但有时我们还是会这么做

有没有办法防止Python查找当前范围之外的变量?(因此,在第二个示例中,访问ab会引发错误。)


Tags: 代码return定义过程deflineresult函数参数
3条回答

在@skyking的回答下,我无法访问任何导入(我甚至不能使用print)。此外,具有可选参数的函数也被破坏(比较How can an optional parameter become required?

@Ax3l的评论稍微改善了这一点。我仍然无法访问导入的变量(from module import var

因此,我建议:

def noglobal(f):
    return types.FunctionType(f.__code__, globals().copy(), f.__name__, f.__defaults__, f.__closure__)

对于每个用@noglobal修饰的函数,它将创建迄今定义的globals()的副本。这样就可以访问导入的变量(通常在文档顶部导入)。如果您像我一样,先定义函数,然后定义变量,这将达到预期效果,即能够访问函数中导入的变量,而不是代码中定义的变量。因为copy()创建了一个浅拷贝(Understanding dict.copy() - shallow or deep?),所以这也应该是非常高效的内存

注意,通过这种方式,函数只能调用在其自身之上定义的函数,因此您可能需要对代码重新排序

为了记录在案,我从his Gist复制了@Ax3l的版本:

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()))

不,您不能告诉Python不要在全局范围内查找名称

如果可以,您将无法使用模块中定义的任何其他类或函数,无法从其他模块导入对象,也无法使用内置名称。函数名称空间变成了一片沙漠,几乎没有它所需要的一切,唯一的出路是将所有内容导入本地名称空间用于模块中的每个函数

与其尝试中断全局查找,不如保持全局命名空间干净。不要添加不需要与模块中的其他作用域共享的全局变量。例如,使用main()函数来封装真正的局部变量

另外,添加单元测试。在没有(甚至只有少数)测试的情况下进行重构总是容易产生bug

是的,也许不是一般的。但是,您可以使用函数来实现

您要做的是使函数的全局值为空。您不能替换globals,也不想修改它的内容(因为 这只是为了摆脱全局变量和函数)

但是:您可以在运行时创建函数对象。构造函数看起来像types.FunctionType((code, globals[, name[, argdefs[, closure]]])。您可以在此处替换全局命名空间:

def line(x, a0, b0):
   return a + x * b  # will be an error

a, b = 1, 1
y1 = line(1, a, b)  # correct result by coincidence

line = types.FunctionType(line.__code__, {})
y1 = line(1, a, b)  # fails since global name is not defined

当然,您可以通过定义自己的装饰器来解决这个问题:

import types
noglobal = lambda f: types.FunctionType(f.__code__, {}, argdefs=f.__defaults__)

@noglobal
def f():
    return x

x = 5
f() # will fail

严格地说,您并不禁止它访问全局变量,您只是让函数相信全局名称空间中没有变量。实际上,您也可以使用它来模拟静态变量,因为如果它将一个变量声明为全局变量并分配给它,那么它将在它自己的全局命名空间沙盒中结束

如果希望能够访问全局名称空间的一部分,则需要使用希望看到的内容填充函数全局沙盒

相关问题 更多 >