首先,您可能不想这样做。正如 Martijn Pieters 所指出的,很多东西,比如顶级函数和类,都是全局的。
您可以仅过滤不可调用的全局变量。从 C 扩展模块导入的函数、类、内置函数或方法等都是可调用的。您可能还想过滤掉模块(任何你import
是全局的)。这仍然无法捕捉到您在def
. 您可以为此添加某种白名单(这也将允许您创建可以在没有警告的情况下使用的全局“常量”)。真的,你想出的任何东西充其量只是一个非常粗略的指南,而不是你想当作绝对警告的东西。
此外,无论您如何操作,尝试检测隐式全局访问,而不是显式访问(使用global
语句)都将非常困难,所以希望这并不重要。
没有明显的方法可以在源代码级别检测全局变量的所有隐式使用。
但是,从解释器内部进行反射非常容易。
该inspect
模块的文档有一个漂亮的图表,向您展示了各种类型的标准成员。请注意,其中一些在Python 2.x和Python 3.x中有不同的名称。
此函数将为您提供两个版本中绑定方法、未绑定方法、函数或代码对象访问的所有全局名称的列表:
def get_globals(thing):
thing = getattr(thing, 'im_func', thing)
thing = getattr(thing, '__func__', thing)
thing = getattr(thing, 'func_code', thing)
thing = getattr(thing, '__code__', thing)
return thing.co_names
如果你只想处理不可调用的,你可以过滤它:
def get_callable_globals(thing):
thing = getattr(thing, 'im_func', thing)
func_globals = getattr(thing, 'func_globals', {})
thing = getattr(thing, 'func_code', thing)
return [name for name in thing.co_names
if callable(func_globals.get(name))]
这并不完美(例如,如果一个函数的全局变量有一个自定义的内置替换,我们将无法正确查找它),但它可能已经足够好了。
一个简单的使用例子:
>>> def foo(myparam):
... myglobal
... mylocal = 1
>>> print get_globals(foo)
('myglobal',)
你可以很容易地import
一个模块并递归地遍历它的可调用对象并调用get_globals()
每个模块,这将适用于主要情况(顶级函数,以及顶级和嵌套类的方法),尽管它不适用于任何事情动态定义(例如,函数内部定义的函数或类)。
如果您只关心 CPython,另一种选择是使用dis
模块扫描模块或 .pyc 文件(或类或其他)中的所有字节码,并记录每个LOAD_GLOBAL
操作。
与该inspect
方法相比,它的一个主要优点是它会找到已编译的函数,即使它们尚未创建。
缺点是无法查找名称(如果其中一些尚未创建,怎么可能?),因此您无法轻松过滤掉可调用对象。你可以尝试做一些花哨的事情,比如将LOAD_GLOBAL
操作连接到相应的CALL_FUNCTION
(和相关的)操作,但是……这开始变得相当复杂。
最后,如果你想动态地钩住东西,你总是可以globals
用一个每次访问它时发出警告的包装器来替换。例如:
class GlobalsWrapper(collections.MutableMapping):
def __init__(self, globaldict):
self.globaldict = globaldict
# ... implement at least __setitem__, __delitem__, __iter__, __len__
# in the obvious way, by delegating to self.globaldict
def __getitem__(self, key):
print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
return self.globaldict[key]
globals_wrapper = GlobalsWrapper(globals())
同样,您可以很容易地过滤不可调用的对象:
def __getitem__(self, key):
value = self.globaldict[key]
if not callable(value):
print >>sys.stderr, 'Warning: accessing global "{}"'.format(key)
return value
显然,对于 Python 3,您需要将print
语句更改为print
函数调用。
您也可以很容易地引发异常而不是警告。或者您可能要考虑使用该warnings
模块。
您可以通过各种不同的方式将其挂接到您的代码中。最明显的是一个导入钩子,它为每个新模块GlobalsWrapper
围绕其正常构建的globals
. 虽然我不确定这将如何与 C 扩展模块交互,但我的猜测是它要么工作,要么被无害地忽略,其中任何一个都可能没问题。唯一的问题是这不会影响您的顶级脚本。如果这很重要,您可以编写一个包装器脚本,它execfile
是带有 aGlobalsWrapper
或类似内容的主脚本。