在 Python 中禁用全局变量查找
Disable global variable lookup in 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查找当前作用域外的变量? (因此访问 a
或 b
会在第二个示例中引发错误。)
不,你不能告诉 Python 不要在全局范围内查找名称。
如果可以,您将无法使用任何其他类或模块中定义的函数,没有从其他模块导入的对象,您也不能使用内置名称。你的函数命名空间变成了一片几乎没有它需要的一切的沙漠,唯一的出路就是将所有东西导入本地命名空间。 对于模块中的每个函数。
与其尝试破坏全局查找,不如保持全局命名空间干净。不要添加不需要与模块中的其他作用域共享的全局变量。例如,使用 main()
函数来封装真正的局部变量。
此外,添加单元测试。没有(即使只有少数)测试的重构总是容易产生错误。
要阻止全局变量查找,请将您的函数移动到另一个模块中。除非它检查调用堆栈或显式导入您的调用模块;它无法从调用它的模块访问全局变量。
实际上,将您的代码移动到 main()
函数中,以避免创建不必要的全局变量。
如果因为多个函数需要操作共享状态而使用全局变量,则将代码移至 class。
是的,一般情况下可能不会。但是你可以用函数来做到这一点。
您要做的是让函数的全局变量为空。您不能替换全局变量,也不想修改它的内容(因为
那只是为了摆脱全局变量和函数)。
但是:您可以在运行时创建函数对象。构造函数看起来像 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
严格来说你并没有禁止它访问全局变量,你只是让函数相信全局命名空间中没有变量。实际上,您也可以使用它来模拟静态变量,因为如果它声明一个变量为全局变量并分配给它,它将最终进入它自己的全局命名空间沙箱。
如果您希望能够访问部分全局命名空间,则需要使用您希望它看到的内容填充函数全局沙箱。
理论上,您可以使用自己的装饰器在函数调用时移除 globals()
。隐藏所有 globals()
会产生一些开销,但是,如果 globals()
不是太多,它可能会有用。在操作期间我们不 create/remove 全局对象,我们只是覆盖字典中引用全局对象的引用。但不要删除特殊的 globals()
(如 __builtins__
)和模块。可能您也不想从全局范围中删除 callable
s。
from types import ModuleType
import re
# the decorator to hide global variables
def noglobs(f):
def inner(*args, **kwargs):
RE_NOREPLACE = '__\w+__'
old_globals = {}
# removing keys from globals() storing global values in old_globals
for key, val in globals().iteritems():
if re.match(RE_NOREPLACE, key) is None and not isinstance(val, ModuleType) and not callable(val):
old_globals.update({key: val})
for key in old_globals.keys():
del globals()[key]
result = f(*args, **kwargs)
# restoring globals
for key in old_globals.iterkeys():
globals()[key] = old_globals[key]
return result
return inner
# the example of usage
global_var = 'hello'
@noglobs
def no_globals_func():
try:
print 'Can I use %s here?' % global_var
except NameError:
print 'Name "global_var" in unavailable here'
def globals_func():
print 'Can I use %s here?' % global_var
globals_func()
no_globals_func()
print 'Can I use %s here?' % global_var
...
Can I use hello here?
Name "global_var" in unavailable here
Can I use hello here?
或者,您可以遍历模块中的所有全局可调用对象(即函数)并动态装饰它们(代码更多)。
该代码适用于 Python 2
,我认为可以为 Python 3
创建一个非常相似的代码。
对于@skyking 的回答,我无法访问任何导入(我什至无法使用 print
)。此外,带有可选参数的函数已损坏(比较 )。
@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()))
如@bers 所述,@skykings 的装饰器破坏了函数内部的大多数 python 功能,例如 print()
和 import
语句。 @bers 通过在装饰器定义时添加当前从 globals()
导入的模块来绕过 import
语句。
这启发我编写了另一个装饰器,它有望实现大多数前来查看此 post 的人真正想要的功能。潜在的问题是以前的装饰器创建的新函数缺少 __builtins__
变量,该变量包含新打开的解释器中可用的所有标准内置 python 函数(例如 print
) .
import types
import builtins
def no_globals(f):
'''
A function decorator that prevents functions from looking up variables in outer scope.
'''
# need builtins in globals otherwise can't import or print inside the function
new_globals = {'__builtins__': builtins}
new_f = types.FunctionType(f.__code__, globals=new_globals, argdefs=f.__defaults__)
new_f.__annotations__ = f.__annotations__ # for some reason annotations aren't copied over
return new_f
那么用法如下
@no_globals
def f1():
return x
x = 5
f1() # should raise NameError
@no_globals
def f2(x):
import numpy as np
print(x)
return np.sin(x)
x = 5
f2(x) # should print 5 and return -0.9589242746631385
简而言之,问题:有没有办法防止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查找当前作用域外的变量? (因此访问 a
或 b
会在第二个示例中引发错误。)
不,你不能告诉 Python 不要在全局范围内查找名称。
如果可以,您将无法使用任何其他类或模块中定义的函数,没有从其他模块导入的对象,您也不能使用内置名称。你的函数命名空间变成了一片几乎没有它需要的一切的沙漠,唯一的出路就是将所有东西导入本地命名空间。 对于模块中的每个函数。
与其尝试破坏全局查找,不如保持全局命名空间干净。不要添加不需要与模块中的其他作用域共享的全局变量。例如,使用 main()
函数来封装真正的局部变量。
此外,添加单元测试。没有(即使只有少数)测试的重构总是容易产生错误。
要阻止全局变量查找,请将您的函数移动到另一个模块中。除非它检查调用堆栈或显式导入您的调用模块;它无法从调用它的模块访问全局变量。
实际上,将您的代码移动到 main()
函数中,以避免创建不必要的全局变量。
如果因为多个函数需要操作共享状态而使用全局变量,则将代码移至 class。
是的,一般情况下可能不会。但是你可以用函数来做到这一点。
您要做的是让函数的全局变量为空。您不能替换全局变量,也不想修改它的内容(因为 那只是为了摆脱全局变量和函数)。
但是:您可以在运行时创建函数对象。构造函数看起来像 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
严格来说你并没有禁止它访问全局变量,你只是让函数相信全局命名空间中没有变量。实际上,您也可以使用它来模拟静态变量,因为如果它声明一个变量为全局变量并分配给它,它将最终进入它自己的全局命名空间沙箱。
如果您希望能够访问部分全局命名空间,则需要使用您希望它看到的内容填充函数全局沙箱。
理论上,您可以使用自己的装饰器在函数调用时移除 globals()
。隐藏所有 globals()
会产生一些开销,但是,如果 globals()
不是太多,它可能会有用。在操作期间我们不 create/remove 全局对象,我们只是覆盖字典中引用全局对象的引用。但不要删除特殊的 globals()
(如 __builtins__
)和模块。可能您也不想从全局范围中删除 callable
s。
from types import ModuleType
import re
# the decorator to hide global variables
def noglobs(f):
def inner(*args, **kwargs):
RE_NOREPLACE = '__\w+__'
old_globals = {}
# removing keys from globals() storing global values in old_globals
for key, val in globals().iteritems():
if re.match(RE_NOREPLACE, key) is None and not isinstance(val, ModuleType) and not callable(val):
old_globals.update({key: val})
for key in old_globals.keys():
del globals()[key]
result = f(*args, **kwargs)
# restoring globals
for key in old_globals.iterkeys():
globals()[key] = old_globals[key]
return result
return inner
# the example of usage
global_var = 'hello'
@noglobs
def no_globals_func():
try:
print 'Can I use %s here?' % global_var
except NameError:
print 'Name "global_var" in unavailable here'
def globals_func():
print 'Can I use %s here?' % global_var
globals_func()
no_globals_func()
print 'Can I use %s here?' % global_var
...
Can I use hello here?
Name "global_var" in unavailable here
Can I use hello here?
或者,您可以遍历模块中的所有全局可调用对象(即函数)并动态装饰它们(代码更多)。
该代码适用于 Python 2
,我认为可以为 Python 3
创建一个非常相似的代码。
对于@skyking 的回答,我无法访问任何导入(我什至无法使用 print
)。此外,带有可选参数的函数已损坏(比较
@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()))
如@bers 所述,@skykings 的装饰器破坏了函数内部的大多数 python 功能,例如 print()
和 import
语句。 @bers 通过在装饰器定义时添加当前从 globals()
导入的模块来绕过 import
语句。
这启发我编写了另一个装饰器,它有望实现大多数前来查看此 post 的人真正想要的功能。潜在的问题是以前的装饰器创建的新函数缺少 __builtins__
变量,该变量包含新打开的解释器中可用的所有标准内置 python 函数(例如 print
) .
import types
import builtins
def no_globals(f):
'''
A function decorator that prevents functions from looking up variables in outer scope.
'''
# need builtins in globals otherwise can't import or print inside the function
new_globals = {'__builtins__': builtins}
new_f = types.FunctionType(f.__code__, globals=new_globals, argdefs=f.__defaults__)
new_f.__annotations__ = f.__annotations__ # for some reason annotations aren't copied over
return new_f
那么用法如下
@no_globals
def f1():
return x
x = 5
f1() # should raise NameError
@no_globals
def f2(x):
import numpy as np
print(x)
return np.sin(x)
x = 5
f2(x) # should print 5 and return -0.9589242746631385