在Python中,如何获取函数中使用的全局变量?
我正在收集崩溃的信息,但我在弄清楚如何获取崩溃函数中使用的全局变量时遇到了困难。
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