有没有工具/库可以列出在另一个方法/函数中调用的方法/函数列表?
例如: 如果该工具或库针对以下方法运行
def calculate(a: int, b: int, operator: Operator):
if operator == Operator.add:
add(a, b)
elif operator == Operator.subtract
subtract(a, b)
然后它应该返回
1. add
2. subtract
这个问题与this one几乎相同,但这是针对Java的。
这基本上与PyCharm
对Find Usage
所做的相同。
谢谢!
答案 0 :(得分:5)
这似乎可以完成工作:
import dis
def list_func_calls(fn):
funcs = []
bytecode = dis.Bytecode(fn)
instrs = list(reversed([instr for instr in bytecode]))
for (ix, instr) in enumerate(instrs):
if instr.opname=="CALL_FUNCTION":
load_func_instr = instrs[ix + instr.arg + 1]
funcs.append(load_func_instr.argval)
return ["%d. %s" % (ix, funcname) for (ix, funcname) in enumerate(reversed(funcs), 1)]
示例:
>>> list_func_calls(calculate)
['1. add', '2. subtract']
这里发生的是:
我们将逐步浏览列表,并为每条CALL_FUNCTION指令
我们使用指令arg
参数来告诉我们有多少
我们得到的论点
我们回首过去,找到了加载函数的指令 我们在打电话
我们将该函数的名称(instr.argval
)添加到列表中,然后
反转,枚举并以请求的格式返回
请注意,自Python 3.6起,共有3条CALL_FUNCTION
指令,因此您必须查看文档以扩展此示例以使其能够在当前python的情况下完全发挥作用。
答案 1 :(得分:3)
更新:添加了对Python2.7
的兼容性
经过测试并确认可与Python2.7
,Python3.5
和Python3.6
指出指向dis
的信用转到Patrick Haugh¹
我的实现(对dis
输出的解析)是我自己的:
设置:
import dis
import sys
from contextlib import contextmanager
# setup test environment
def a(_,__):
pass
def b(_,__,___):
pass
def c(_):
pass
def g():
pass
d = 4
def test(flag):
e = c
if flag:
a(a(b,c), [l for l in g(1, x=2)])
else:
b(a, int(flag), c(e))
d = d + 1
def calculate(a, b, operator):
if operator == Operator.add:
add(a, b)
elif operator == Operator.subtract:
subtract(a, b)
class Operator(object):
add = "add"
subtract = "subtract"
Python 2/3兼容性:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
@contextmanager # https://stackoverflow.com/a/12111817/2422125
def captureStdOut(output):
stdout = sys.stdout
sys.stdout = output
try:
yield
finally:
sys.stdout = stdout
""" for Python <3.4 """
def get_instructions(func):
import StringIO
out = StringIO.StringIO()
with captureStdOut(out):
dis.dis(func)
return [AttrDict({
'opname': i[16:36].strip(),
'arg': int(i[37:42].strip() or 0),
'argval': i[44:-1].strip()
}) for i in out.getvalue().split("\n")]
if sys.version_info < (3, 4):
dis.get_instructions = get_instructions
import __builtin__ as builtin
else:
import builtins as builtin
代码:
def get_function_calls(func, built_ins=False):
# the used instructions
ins = list(dis.get_instructions(func))[::-1]
# dict for function names (so they are unique)
names = {}
# go through call stack
for i, inst in list(enumerate(ins))[::-1]:
# find last CALL_FUNCTION
if inst.opname[:13] == "CALL_FUNCTION":
# function takes ins[i].arg number of arguments
ep = i + inst.arg + (2 if inst.opname[13:16] == "_KW" else 1)
# parse argument list (Python2)
if inst.arg == 257:
k = i+1
while k < len(ins) and ins[k].opname != "BUILD_LIST":
k += 1
ep = k-1
# LOAD that loaded this function
entry = ins[ep]
# ignore list comprehensions / ...
name = str(entry.argval)
if "." not in name and entry.opname == "LOAD_GLOBAL" and (built_ins or not hasattr(builtin, name)):
# save name of this function
names[name] = True
# reduce this CALL_FUNCTION and all its paramters to one entry
ins = ins[:i] + [entry] + ins[ep + 1:]
return sorted(list(names.keys()))
输出:
> print(get_function_calls(test))
> ['a', 'b', 'c', 'g']
> print(get_function_calls(test, built_ins=True))
> ['a', 'b', 'c', 'g', 'int']
> print(get_function_calls(calculate))
> ['add', 'subtract']
¹由于Patrick Haugh对dis
的评论已超过2小时,因此我认为这是免费服用...