检测python函数中的所有全局变量?

时间:2015-10-16 00:39:04

标签: python function global-variables

我正在尝试分析一些混乱的代码,这恰好在函数中使用全局变量(我试图重构代码,以便函数只使用局部变量)。有没有办法在函数中检测全局变量?

例如:

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

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

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

编辑:如果有人感兴趣,这是我用于检测全局变量的代码(基于kindall的答案和Paolo对此问题的回答: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编写的捕获装饰器并在此处发布:{{3 }}

2 个答案:

答案 0 :(得分: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']

答案 1 :(得分: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',)
相关问题