在Python中,如何获取函数中使用的全局变量?

11 投票
5 回答
2039 浏览
提问于 2025-04-16 14:25

我正在收集崩溃的信息,但我在弄清楚如何获取崩溃函数中使用的全局变量时遇到了困难。

import inspect

fun = 222
other = "junk"

def test():
    global fun
    harold = 888 + fun
    try:
        harold/0
    except:
        frames = inspect.trace()
        print "Local variables:"
        print frames[0][0].f_locals

        print "All global variables, not what I want!"
        print frames[0][0].f_globals

test()

test()这个函数只使用了"fun"这个全局变量,但f_globals却给出了所有可用的全局变量。有没有什么办法只获取这个函数实际使用的全局变量呢?

5 个回答

1

一种比较粗糙的方法是使用 inspect.getsourcelines() 这个函数,然后查找包含 global <varname> 的行。不过在 inspect 模块里,没有更好的方法可以做到这一点。

2

我自己也需要这个功能。这是我的解决方案。非快速路径可以处理大多数你可能感兴趣的情况。

def iterGlobalsUsedInFunc(f, fast=False, loadsOnly=True):
    if hasattr(f, "func_code"): code = f.func_code
    else: code = f
    if fast:
        # co_names is the list of all names which are used.
        # These are mostly the globals. These are also attrib names, so these are more...
        for name in code.co_names:
            yield name
    else:
        # Use the disassembly. Note that this will still not
        # find dynamic lookups to `globals()`
        # (which is anyway not possible to detect always).
        import dis
        ops = ["LOAD_GLOBAL"]
        if not loadsOnly:
            ops += ["STORE_GLOBAL", "DELETE_GLOBAL"]
        ops = map(dis.opmap.__getitem__, ops)
        i = 0
        while i < len(code.co_code):
            op = ord(code.co_code[i])
            i += 1
            if op >= dis.HAVE_ARGUMENT:
                oparg = ord(code.co_code[i]) + ord(code.co_code[i+1])*256
                i += 2
            else:
                oparg = None
            if op in ops:
                name = code.co_names[oparg]
                yield name

    # iterate through sub code objects
    import types
    for subcode in code.co_consts:
        if isinstance(subcode, types.CodeType):
            for g in iterGlobalsUsedInFunc(subcode, fast=fast, loadsOnly=loadsOnly):
                yield g

更新版本可以在 这里 找到。


我的使用场景:

我有一个模块(songdb),里面有一些全局数据库对象。我想在调用使用这些全局数据库变量的函数时,懒加载这些对象。也就是说,我可以手动给这些函数加上懒加载的装饰器,或者通过我的 iterGlobalsUsedInFunc 函数自动检测哪些函数需要懒加载。

这基本上就是代码(完整代码;其实现在已经扩展到类了),其中 init 会自动给这些函数加上装饰器:

DBs = {
    "songDb": "songs.db",
    "songHashDb": "songHashs.db",
    "songSearchIndexDb": "songSearchIndex.db",
    }
for db in DBs.keys(): globals()[db] = None

def usedDbsInFunc(f):
    dbs = []
    for name in utils.iterGlobalsUsedInFunc(f, loadsOnly=True):
        if name in DBs:
            dbs += [name]
    return dbs

def init():
    import types
    for fname in globals().keys():
        f = globals()[fname]
        if not isinstance(f, types.FunctionType): continue
        dbs = usedDbsInFunc(f)
        if not dbs: continue
        globals()[fname] = lazyInitDb(*dbs)(f)

def initDb(db):
    if not globals()[db]:
        globals()[db] = DB(DBs[db])

def lazyInitDb(*dbs):
    def decorator(f):
        def decorated(*args, **kwargs):
            for db in dbs:
                initDb(db)
            return f(*args, **kwargs)
        return decorated
    return decorator

另一种解决方案是使用对象代理,懒加载数据库。我在这个项目的其他地方也用过这个方法,所以我也实现了这样的对象代理;如果你感兴趣,可以在这里查看: utils.py 中的 ObjectProxy

4

看看这个

a = 10

def test():
    global a
    a = 12
    b = 12

print "co_argcount = ",test.__code__.co_argcount
print "co_cellvars = ",test.__code__.co_cellvars
print "co_code = ",test.__code__.co_code
print "co_consts = ",test.__code__.co_consts
print "co_filename = ",test.__code__.co_filename
print "co_firstlineno = ",test.__code__.co_firstlineno
print "co_flags = ",test.__code__.co_flags
print "co_freevars = ",test.__code__.co_freevars
print "co_lnotab = ",test.__code__.co_lnotab
print "co_name = ",test.__code__.co_name
print "co_names = ",test.__code__.co_names
print "co_nlocals = ",test.__code__.co_nlocals
print "co_stacksize = ",test.__code__.co_stacksize
print "co_varnames = ",test.__code__.co_varnames

撰写回答