我正在尝试分析一些混乱的代码,这恰好在函数中使用全局变量(我试图重构代码,以便函数只使用局部变量)。有没有办法在函数中检测全局变量?
例如:
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 }}
答案 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',)