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

Python警告我或阻止我使用全局变量,python,ipython,Python,Ipython,我现在已经有几次在函数或方法定义中意外(无意)引用全局变量而陷入麻烦 我的问题是:有没有办法禁止python让我引用全局变量?或者至少警告我正在引用一个全局变量 x = 123 def myfunc() : print x # throw a warning or something!!! 让我补充一点,对于我的应用程序来说,它出现的典型情况是使用IPython作为交互式shell。我使用“execfile”来执行定义类的脚本。在解释器中,我直接访问类变量以执行一些有用的操作,

我现在已经有几次在函数或方法定义中意外(无意)引用全局变量而陷入麻烦

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

x = 123

def myfunc() :
    print x    # throw a warning or something!!!
让我补充一点,对于我的应用程序来说,它出现的典型情况是使用IPython作为交互式shell。我使用“execfile”来执行定义类的脚本。在解释器中,我直接访问类变量以执行一些有用的操作,然后决定将其作为方法添加到类中。当我在解释器中时,我正在引用类变量。然而,当它成为一种方法时,它需要引用“self”。这里有一个例子

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工作流程提出了建议

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

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

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


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

然而,从解释器内部进行反射非常容易

inspect
模块的文档中有一个漂亮的图表,显示了各种类型的标准成员。请注意,其中一些在和中有不同的名称

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

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',)

您可以非常轻松地
导入
一个模块,递归地遍历其可调用项,并在每个模块上调用
get_globals()
,这将适用于主要情况(顶级函数、顶级和嵌套类的方法),尽管它不适用于任何动态定义的情况(例如,在函数中定义的函数或类)


如果您只关心CPython,另一个选项是使用模块扫描模块或.pyc文件(或类或其他文件)中的所有字节码,并记录每个
LOAD\u GLOBAL
op

inspect
方法相比,这种方法的一个主要优点是,它将查找已编译的函数,即使它们尚未创建

缺点是无法查找名称(如果其中一些名称尚未创建,怎么可能有呢?),因此您无法轻松筛选出可调用项。您可以尝试做一些花哨的事情,例如将
LOAD\u GLOBAL
ops连接到相应的
CALL\u 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
显然,对于Python3,您需要将
print
语句更改为
print
函数调用

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


您可以通过各种不同的方式将其挂接到代码中。最明显的一种是导入挂接,它为每个新模块提供一个围绕其正常构建的
GlobalsWrapper
。虽然我不确定这将如何与C扩展模块交互,但我猜它要么工作,要么被无害地忽略她这样做可能没问题。唯一的问题是这不会影响你的顶级脚本。如果这很重要,你可以编写一个包装器脚本,用一个
GlobalsWrapper
或类似的东西作为主脚本并创建以限制功能范围

>>从localscope导入localscope
>>>a=‘你好,世界’
>>>@localscope
…def print_a():
…打印(a)
回溯(最近一次呼叫最后一次):
...
ValueError:`a`不是允许的全局变量
@localscope
装饰器使用python的反汇编程序,使用
LOAD\u GLOBAL
(全局变量访问)或
LOAD\u DEREF
(闭包访问)查找装饰函数的所有实例语句。如果要加载的变量是内置函数、显式列为异常或满足谓词,则允许使用该变量。否则,