我现在已经陷入困境几次,无意中(无意中)在函数或方法定义中引用了全局变量.
我的问题是:有没有办法禁止python让我引用一个全局变量?或者至少警告我,我正在引用一个全局变量?
x = 123
def myfunc() :
print x # throw a warning or something!!!
Run Code Online (Sandbox Code Playgroud)
让我补充说,这对我来说典型的情况是使用IPython作为交互式shell.我使用'execfile'来执行定义类的脚本.在解释器中,我直接访问类变量来做一些有用的事情,然后决定我想在我的类中添加它作为一个方法.当我在解释器中时,我正在引用类变量.但是,当它成为一种方法时,它需要引用"自我".这是一个例子.
class MyClass :
a = 1
b = 2
def add(self) :
return a+b
m = MyClass()
Run Code Online (Sandbox Code Playgroud)
现在在我的解释器中运行脚本'execfile('script.py')',我正在检查我的类并输入:'ma*mb'并决定,这将是一个有用的方法.所以我修改我的代码,非故意复制/粘贴错误:
class MyClass :
a = 1
b = 2
def add(self) :
return a+b
def mult(self) :
return m.a * m.b # I really meant this to be self.a * self.b
Run Code Online (Sandbox Code Playgroud)
这当然仍然在IPython中执行,但它真的让我很困惑,因为它现在引用了先前定义的全局变量!
根据我的典型IPython工作流程,也许有人有建议.
首先,您可能不想这样做。正如 Martijn Pieters 指出的那样,许多东西,比如顶级函数和类,都是全局的。
\n\n您可以仅过滤不可调用的全局变量。从 C 扩展模块导入的函数、类、内置函数或方法等都是可调用的。您可能还想过滤掉模块(任何属于import全局的模块)。这仍然无法捕获您将函数分配给def. 您可以为此添加某种白名单(这也允许您创建可以在没有警告的情况下使用的全局“常量”)。实际上,您提出的任何内容充其量只是一个非常粗略的指南,而不是您希望将其视为绝对警告的内容。
另外,无论您如何执行此操作,尝试检测隐式全局访问,但不检测显式访问(使用语句global)都将非常困难,因此希望这并不重要。
没有明显的方法可以在源级别检测全局变量的所有隐式使用。
\n\n然而,通过解释器内部的反射可以很容易地做到这一点。
\n\n该inspect模块的文档有一个很好的图表,向您展示了各种类型的标准成员。请注意,其中一些在Python 2.x和Python 3.x中具有不同的名称。
此函数将为您提供两个版本中的绑定方法、未绑定方法、函数或代码对象访问的所有全局名称的列表:
\n\ndef get_globals(thing):\n thing = getattr(thing, \'im_func\', thing)\n thing = getattr(thing, \'__func__\', thing)\n thing = getattr(thing, \'func_code\', thing)\n thing = getattr(thing, \'__code__\', thing)\n return thing.co_names\nRun Code Online (Sandbox Code Playgroud)\n\n如果你只想处理不可调用的,你可以过滤它:
\n\ndef get_callable_globals(thing):\n thing = getattr(thing, \'im_func\', thing)\n func_globals = getattr(thing, \'func_globals\', {})\n thing = getattr(thing, \'func_code\', thing)\n return [name for name in thing.co_names\n if callable(func_globals.get(name))]\nRun Code Online (Sandbox Code Playgroud)\n\n这并不完美(例如,如果一个函数的全局变量有一个自定义的内置替换,我们将无法正确查找它),但它可能已经足够好了。
\n\n使用它的一个简单示例:
\n\n>>> def foo(myparam):\n... myglobal\n... mylocal = 1\n>>> print get_globals(foo)\n(\'myglobal\',)\nRun Code Online (Sandbox Code Playgroud)\n\n您可以很容易地创建import一个模块,并递归地遍历其可调用对象并调用get_globals()每个可调用对象,这将适用于主要情况(顶级函数以及顶级和嵌套类的方法),尽管它不适用于动态定义的任何内容(例如,在函数内部定义的函数或类)。
如果您只关心 CPython,另一种选择是使用dis模块扫描模块或 .pyc 文件(或类或其他文件)中的所有字节码,并记录每个LOAD_GLOBAL操作。
与该inspect方法相比,此方法的一个主要优点是它会找到已编译的函数,即使它们尚未创建。
缺点是无法查找名称(如果其中一些名称还没有创建,怎么可能有名称?),因此您无法轻松过滤掉可调用项。您可以尝试做一些奇特的事情,例如将LOAD_GLOBAL操作连接到相应的CALL_FUNCTION(和相关的)操作,但是\xe2\x80\xa6\xc2\xa0\开始变得相当复杂。
最后,如果您想动态挂钩,您可以随时替换globals为每次访问时都会发出警告的包装器。例如:
class GlobalsWrapper(collections.MutableMapping):\n def __init__(self, globaldict):\n self.globaldict = globaldict\n # ... implement at least __setitem__, __delitem__, __iter__, __len__\n # in the obvious way, by delegating to self.globaldict\n def __getitem__(self, key):\n print >>sys.stderr, \'Warning: accessing global "{}"\'.format(key)\n return self.globaldict[key]\n\nglobals_wrapper = GlobalsWrapper(globals())\nRun Code Online (Sandbox Code Playgroud)\n\n同样,您可以非常轻松地过滤不可调用的内容:
\n\n def __getitem__(self, key):\n value = self.globaldict[key]\n if not callable(value):\n print >>sys.stderr, \'Warning: accessing global "{}"\'.format(key)\n return value\nRun Code Online (Sandbox Code Playgroud)\n\n显然,对于 Python 3,您需要将print语句更改为print函数调用。
您还可以很容易地引发异常而不是警告。或者您可能想考虑使用该warnings模块。
您可以通过各种不同的方式将其挂接到您的代码中。最明显的是一个导入钩子,它为每个新模块提供了一个GlobalsWrapper围绕其正常构建的globals. 虽然我不确定它将如何与 C 扩展模块交互,但我的猜测是它要么起作用,要么被无害地忽略,两者都可能没问题。唯一的问题是这不会影响您的顶级脚本。如果这很重要,您可以编写一个包装脚本作为execfile主脚本GlobalsWrapper,并带有 , 或类似的内容。
| 归档时间: |
|
| 查看次数: |
1416 次 |
| 最近记录: |