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

时间:2013-05-23 18:40:56

标签: python ipython

我现在遇到了几次麻烦,无意中(无意中)在函数或方法定义中引用了全局变量。

我的问题是:有没有办法禁止python让我引用一个全局变量?或者至少警告我,我引用了一个全局变量?

x = 123

def myfunc() :
    print x    # throw a warning or something!!!

让我补充一点,这对我来说是典型的情况是使用IPython作为交互式shell。我使用'execfile'来执行定义类的脚本。在解释器中,我直接访问类变量来做一些有用的事情,然后决定我想在我的类中添加它作为一个方法。当我在解释器中时,我正在引用类变量。但是,当它成为一种方法时,它需要引用“自我”。这是一个例子。

class MyClass :

    a = 1
    b = 2

    def add(self) :
        return a+b


m = MyClass()

现在在我的解释器中运行脚本'execfile('script.py')',我正在检查我的类并输入:'m.a * m.b'然后决定,这将是一个有用的方法。所以我修改了我的代码,非故意的复制/粘贴错误:

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

这当然仍然在IPython中执行,但它真的让我感到困惑,因为它现在引用了先前定义的全局变量!

根据我的典型IPython工作流程,也许有人有建议。

2 个答案:

答案 0 :(得分:4)

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

您可以仅针对不可调用的全局变量进行过滤。从C扩展模块导入的函数,类,内置函数或方法等都是可调用的。您可能还想过滤掉模块(import是全局的)。那仍然不会发现你在def之后将一个函数分配给另一个名字的情况。您可以为此添加某种白名单(这也可以让您创建可以在没有警告的情况下使用的全局“常量”)。实际上,你提出的任何内容都将是一个非常粗略的指南,而不是你想要作为绝对警告的东西。

此外,无论你如何做,尝试检测隐式全局访问,但不是显式访问(使用global语句)将非常困难,所以希望这并不重要。


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

但是,从解释器内部进行反射很容易。

inspect模块的文档有一个很好的图表,可以显示各种类型的标准成员。请注意,其中一些名称在Python 2.xPython 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一个模块并递归地遍历它的callables并在每个模块上调用get_globals(),这将适用于主要情况(顶级函数和顶级方法)虽然它不适用于动态定义的任何东西(例如,函数内部定义的函数或类)。


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

这比LOAD_GLOBAL方法的一个主要优点是它可以找到已编译的函数,即使它们尚未创建。

缺点是没有办法查找名称(怎么可能,如果有些甚至还没有创建?),所以你不能轻易过滤掉callables。您可以尝试做一些花哨的事情,比如将inspect操作系统连接到相应的LOAD_GLOBAL(以及相关的)操作系统,但是......开始变得相当复杂。


最后,如果你想动态地挂钩,你可以随时用一个包装器替换CALL_FUNCTION,每次访问它时都会发出警告。例如:

globals

同样,您可以非常轻松地过滤非callables:

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

显然,对于Python 3,您需要将 def __getitem__(self, key): value = self.globaldict[key] if not callable(value): print >>sys.stderr, 'Warning: accessing global "{}"'.format(key) return value 语句更改为print函数调用。

您也可以轻松提出异常而非警告。或者您可能要考虑使用warnings模块。

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

答案 1 :(得分:1)

我一直在努力应对类似的挑战(尤其是在Jupyter笔记本电脑中),并创建了a small package来限制功能范围。

>>> from localscope import localscope
>>> a = 'hello world'
>>> @localscope
... def print_a():
...     print(a)
Traceback (most recent call last):
  ...
ValueError: `a` is not a permitted global

@localscope装饰器使用python的反汇编程序通过LOAD_GLOBAL(全局变量访问)或LOAD_DEREF(关闭访问)语句查找装饰函数的所有实例。如果要加载的变量是内置函数,被明确列出为异常或满足谓词,则允许该变量。否则,将引发异常。

请注意,装饰器会静态分析代码。因此,它无权访问由闭包访问的变量的值。