【问题标题】:Detect all global variables within a python function?检测python函数中的所有全局变量?
【发布时间】:2015-10-16 00:39:04
【问题描述】:

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

例如:

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 编写的捕获装饰器并在此处发布:Capture stdout from a script in Python

【问题讨论】:

标签: python function global-variables


【解决方案1】:

检查字节码。

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']

【讨论】:

  • 您对使用 print(globals()) 有何看法?
  • 这很大程度上取决于状态,即,哪些全局变量已由您完成的特定函数调用序列定义(假设某些函数设置了全局变量)。 dis 更安全,因为 Python 解析器在生成字节码时已经决定哪些变量是局部变量,因此即使尚未定义,它也知道哪些必须是全局变量。
  • 太棒了!那是我正在寻找的简短而甜蜜的pythonic答案。 dis 看起来是一个非常酷的库,稍后我将不得不深入研究它。 @idjaw print(globals()) 将打印脚本中的所有全局变量,而不仅仅是感兴趣的函数中的全局变量。
  • @applecider:用一个扫描函数字节码并返回它使用的全局变量列表的函数更新了我的答案。
  • @applecider:至于dis有什么用处,主要是看看Python把你的代码变成了什么样的原始指令,或者比较两种做某事的方式来看看哪种使用更少的原始操作很方便。
【解决方案2】:

LOAD_GLOBAL documentation中提到的:

LOAD_GLOBAL(namei)

将名为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',)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-07
    • 2012-05-22
    • 1970-01-01
    • 2014-12-01
    • 2013-05-06
    相关资源
    最近更新 更多