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

11

我正在尝试分析一些混乱的代码,该代码在函数内部使用全局变量非常频繁(我正在尝试重构代码,使函数仅使用局部变量)。有没有办法检测函数内的全局变量呢?

例如:

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

由于未在参数中给出,也未在函数内创建,因此全局变量在此处为y

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

编辑:如果有人感兴趣,这是我用来检测全局变量的代码(基于kindall的答案和Paolo对此问题的答案:在Python中捕获脚本的stdout):

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 装饰器:Capture stdout from a script in Python


恰好我也写了一种捕获stdout的方法。:-) https://dev59.com/Z2Qn5IYBdhLWcg3wxZri#16571630 - kindall
2个回答

9

检查字节码。

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
这很大程度上取决于状态,也就是已定义了哪些全局变量,即假设某些函数设置了全局变量的特定函数调用序列(dis)更安全,因为Python解析器在生成字节码时已经决定了哪些变量是本地变量,所以它知道哪些变量必须是全局变量,即使它们尚未被定义。 - kindall
1
太棒了!那就是我一直在寻找的简短而精炼的Pythonic答案。dis看起来是一个非常酷的库,我以后会深入研究它。@idjaw print(globals())将打印脚本中的所有全局变量,而不仅仅是感兴趣的函数内部的变量。 - applecider
@kindall 我已经添加了一个完整的解决方案来回答这个问题,它只返回函数中的全局变量(更具体地说,只打印与全局变量对应的 dis 输出行)。只是好奇,dis 通常用于什么?还有其他明显的实际应用吗? - applecider
1
@applecider:更新了我的答案,加入了一个函数,可以扫描函数的字节码并返回它使用的全局变量列表。 - kindall
1
@applecider:至于dis有什么用处,主要是方便查看Python将您的代码转换为哪些原始指令,或比较两种做法以查看哪种使用了更少的原始操作。 - kindall
显示剩余2条评论

4
LOAD_GLOBAL文档所述:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接