2015-10-16 29 views
7

我想分析一些凌乱的代码,这恰好在函数中使用全局变量相当沉重(我试图重构代码,以便函数只使用局部变量)。有什么方法可以检测函数内的全局变量吗?检测python函数中的所有全局变量?

例如:

def f(x): 
    x = x + 1 
    z = x + y 
    return z 

这里全局变量是y,因为它不作为参数给出的,并且也不是该函数内创建的。

我试图使用字符串解析来检测函数内的全局变量,但它变得有点混乱;我想知道是否有更好的方法来做到这一点?

编辑:如果有人有兴趣,这是我使用来检测全局变量的代码(基于kindall的答案和保罗的回答这个问题:Capture stdout from a script in Python):

from dis import dis 

def capture(f): 
    """ 
    Decorator to capture standard output 
    """ 
    def captured(*args, **kwargs): 
     import sys 
     from cStringIO import StringIO 

     # setup the environment 
     backup = sys.stdout 

     try: 
      sys.stdout = StringIO()  # capture output 
      f(*args, **kwargs) 
      out = sys.stdout.getvalue() # release output 
     finally: 
      sys.stdout.close() # close the stream 
      sys.stdout = backup # restore original stdout 

     return out # captured output wrapped in a string 

    return captured 

def return_globals(f): 
    """ 
    Prints all of the global variables in function f 
    """ 
    x = dis_(f) 
    for i in x.splitlines(): 
     if "LOAD_GLOBAL" in i: 
      print i 

dis_ = capture(dis) 

dis_(f) 

dis默认情况下不返回输出,所以如果你想操作dis作为一个字符串的输出,你必须使用Paolo写的捕获装饰器,并发布在这里:Capture stdout from a script in Python

+0

碰巧我还写了一种捕获stdout的方法。 :-) http://stackoverflow.com/a/16571630/416467 – kindall

回答

7

检查字节码。

from dis import dis 
dis(f) 

结果:

2   0 LOAD_FAST    0 (x) 
       3 LOAD_CONST    1 (1) 
       6 BINARY_ADD 
       7 STORE_FAST    0 (x) 

    3   10 LOAD_FAST    0 (x) 
      13 LOAD_GLOBAL    0 (y) 
      16 BINARY_ADD 
      17 STORE_FAST    1 (z) 

    4   20 LOAD_FAST    1 (z) 
      23 RETURN_VALUE 

全局变量将有LOAD_GLOBAL操作码,而不是LOAD_FAST。 (如果函数改变任何全局变量,会有STORE_GLOBAL操作码为好。)

随着一点点的工作,你甚至可以写扫描功能的字节码,并返回它使用全局变量列表的功能。事实上:

from dis import HAVE_ARGUMENT, opmap 

def getglobals(func): 
    GLOBAL_OPS = opmap["LOAD_GLOBAL"], opmap["STORE_GLOBAL"] 
    EXTENDED_ARG = opmap["EXTENDED_ARG"] 

    func = getattr(func, "im_func", func) 
    code = func.func_code 
    names = code.co_names 

    op = (ord(c) for c in code.co_code) 
    globs = set() 
    extarg = 0 

    for c in op: 
     if c in GLOBAL_OPS: 
      globs.add(names[next(op) + next(op) * 256 + extarg]) 
     elif c == EXTENDED_ARG: 
      extarg = (next(op) + next(op) * 256) * 65536 
      continue 
     elif c >= HAVE_ARGUMENT: 
      next(op) 
      next(op) 

     extarg = 0 

    return sorted(globs) 

print getglobals(f)    # ['y'] 
+0

你对使用print(globals())有什么想法? – idjaw

+1

这很大程度上取决于状态,也就是说,哪些全局变量已被您所做的特定函数调用序列(假定某些函数设置为全局变量)定义。'dis'更安全,因为Python解析器在生成字节码时已经决定了哪些变量是局部变量,所以它知道哪些变量必须是全局变量,即使它们还没有被定义。 – kindall

+1

太棒了!那是我寻找的简短的甜蜜pythonic答案。 'dis'看起来像一个非常酷的图书馆,我将不得不在后面深入研究。 @idjaw'print(globals())'会打印脚本中的所有全局变量,而不仅仅是那些感兴趣的函数。 – applecider

2

正如LOAD_GLOBAL documentation提到:

LOAD_GLOBAL(对虾)

加载全局命名co_names[namei]到堆栈中。

这意味着你可以检查代码对象为你的功能查找全局:

>>> f.__code__.co_names 
('y',) 

注意,这是不够的嵌套函数(也不是@ kindall的答案dis.dis方法)。在这种情况下,您也需要查看常量:

# Define a function containing a nested function 
>>> def foo(): 
... def bar(): 
...  return some_global 

# It doesn't contain LOAD_GLOBAL, so .co_names is empty. 
>>> dis.dis(foo) 
    2   0 LOAD_CONST    1 (<code object bar at 0x2b70440c84b0, file "<ipython-input-106-77ead3dc3fb7>", line 2>) 
       3 MAKE_FUNCTION   0 
       6 STORE_FAST    0 (bar) 
       9 LOAD_CONST    0 (None) 
      12 RETURN_VALUE 

# Instead, we need to walk the constants to find nested functions: 
# (if bar contain a nested function too, we'd need to recurse) 
>>> from types import CodeType 
>>> for constant in foo.__code__.co_consts: 
...  if isinstance(constant, CodeType): 
...   print constant.co_names 
('some_global',) 
+0

这是关于嵌套函数的一个好点。 – kindall