Python警告我或阻止我使用全局变量

Epi*_*Adv 7 python ipython

我现在已经陷入困境几次,无意中(无意中)在函数或方法定义中引用了全局变量.

我的问题是:有没有办法禁止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工作流程,也许有人有建议.

aba*_*ert 4

首先,您可能不想这样做。正如 Martijn Pieters 指出的那样,许多东西,比如顶级函数和类,都是全局的。

\n\n

您可以仅过滤不可调用的全局变量。从 C 扩展模块导入的函数、类、内置函数或方法等都是可调用的。您可能还想过滤掉模块(任何属于import全局的模块)。这仍然无法捕获您将函数分配给def. 您可以为此添加某种白名单(这也允许您创建可以在没有警告的情况下使用的全局“常量”)。实际上,您提出的任何内容充其量只是一个非常粗略的指南,而不是您希望将其视为绝对警告的内容。

\n\n

另外,无论您如何执行此操作,尝试检测隐式全局访问,但不检测显式访问(使用语句global)都将非常困难,因此希望这并不重要。

\n\n
\n\n

没有明显的方法可以在源级别检测全局变量的所有隐式使用。

\n\n

然而,通过解释器内部的反射可以很容易地做到这一点。

\n\n

inspect模块的文档有一个很好的图表,向您展示了各种类型的标准成员。请注意,其中一些在Python 2.xPython 3.x中具有不同的名称。

\n\n

此函数将为您提供两个版本中的绑定方法、未绑定方法、函数或代码对象访问的所有全局名称的列表:

\n\n
def 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\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

如果你只想处理不可调用的,你可以过滤它:

\n\n
def 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))]\n
Run Code Online (Sandbox Code Playgroud)\n\n

这并不完美(例如,如果一个函数的全局变量有一个自定义的内置替换,我们将无法正确查找它),但它可能已经足够好了。

\n\n
\n\n

使用它的一个简单示例:

\n\n
>>> def foo(myparam):\n...     myglobal\n...     mylocal = 1\n>>> print get_globals(foo)\n(\'myglobal\',)\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

您可以很容易地创建import一个模块,并递归地遍历其可调用对象并调用get_globals()每个可调用对象,这将适用于主要情况(顶级函数以及顶级和嵌套类的方法),尽管它不适用于动态定义的任何内容(例如,在函数内部定义的函数或类)。

\n\n
\n\n

如果您只关心 CPython,另一种选择是使用dis模块扫描模块或 .pyc 文件(或类或其他文件)中的所有字节码,并记录每个LOAD_GLOBAL操作。

\n\n

与该inspect方法相比,此方法的一个主要优点是它会找到已编译的函数,即使它们尚未创建。

\n\n

缺点是无法查找名称(如果其中一些名称还没有创建,怎么可能有名称?),因此您无法轻松过滤掉可调用项。您可以尝试做一些奇特的事情,例如将LOAD_GLOBAL操作连接到相应的CALL_FUNCTION(和相关的)操作,但是\xe2\x80\xa6\xc2\xa0\开始变得相当复杂。

\n\n
\n\n

最后,如果您想动态挂钩,您可以随时替换globals为每次访问时都会发出警告的包装器。例如:

\n\n
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())\n
Run 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

显然,对于 Python 3,您需要将print语句更改为print函数调用。

\n\n

您还可以很容易地引发异常而不是警告。或者您可能想考虑使用该warnings模块。

\n\n

您可以通过各种不同的方式将其挂接到您的代码中。最明显的是一个导入钩子,它为每个新模块提供了一个GlobalsWrapper围绕其正常构建的globals. 虽然我不确定它将如何与 C 扩展模块交互,但我的猜测是它要么起作用,要么被无害地忽略,两者都可能没问题。唯一的问题是这不会影响您的顶级脚本。如果这很重要,您可以编写一个包装脚本作为execfile主脚本GlobalsWrapper,并带有 , 或类似的内容。

\n