Python 在检查/分解代码对象时递归地动态跟踪可调用项

Python 在检查/分解代码对象时递归地动态跟踪可调用项,python,Python,我想对任何函数/方法进行一些反思。对于我所有的例子,我都在使用Python2.7,但是如果使用3.3使事情变得更简单的话,那么使用3.3并不是一个问题 假设我在名为foobar.py的模块中有以下代码: def foo(): bar() 我可以动态地看到foo运行的代码: import inspect import foobar inspect.getsource(foobar.foo) 我还可以通过以下方法从该函数的code对象获取反汇编字节码: import dis dis.di

我想对任何函数/方法进行一些反思。对于我所有的例子,我都在使用Python2.7,但是如果使用3.3使事情变得更简单的话,那么使用3.3并不是一个问题

假设我在名为foobar.py的模块中有以下代码:

def foo():
    bar()
我可以动态地看到foo运行的代码:

import inspect
import foobar
inspect.getsource(foobar.foo)
我还可以通过以下方法从该函数的code对象获取反汇编字节码:

import dis
dis.dis(foobar.foo)
是否有一种方法可以检测到
foo
方法调用另一个函数(
bar
,在本例中),然后动态地分解/检查它

我知道code对象本身有如下各种属性:

>>> dir(foobar.foo.__code__)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
我已经检查了他们中的大多数,只是四处看看,但还没有完全找到我要找的东西

最终目标只是一个小小的实验,看看我是否可以递归地打印出一个可能的调用堆栈,而不必执行导入以外的代码。我知道理论上的调用堆栈无法解释运行时的情况,如特定变量的状态等。我只想打印出给定某个调用的所有嵌套函数的源代码(即使代码永远不会基于运行时状态执行案例)


另外,我知道一旦我进入CPython代码,检查和dis模块也帮不了我。最终,打印出某种映射可能会很有趣,该映射显示了当
inspect
dis
发生故障时CPython代码的内容。然而,我甚至不确定这是否可行。

所有编译器/解释器在解析源代码时都会构建一个抽象语法树。这是基于其上下文无关语法的程序表示,然后可以递归地遍历该语法以生成可由机器执行的代码

Python提供对其AST的访问,您可以自己遍历此树,并在
AST.FunctionDef
中查找
AST.Call
对象。下面粘贴了一个简单的示例。但是请注意,这肯定不会捕获所有可能的调用,因为调用可以嵌入到其他表达式中,由
eval
表达式等隐藏。下面是一个简单的示例:

import ast

source = """
def foo():
    bar()

def bar():
    baz()

def baz():
    print "hello!"
"""

def get_method_name_for_call(call_obj):
    for fieldname, value in ast.iter_fields(call_obj):
        if fieldname == "func":
            return value.id

def walk_method_calls(node, cur_func):
    if not node:
        return

    for cur_node in ast.iter_child_nodes(node):
        if type(cur_node) == ast.Call:
            method_called = get_method_name_for_call(cur_node)
            print "Found a call to %s in body of %s." % (method_called, cur_func)
        walk_method_calls(cur_node, cur_func)


def walk_function_defs(node):
    if not node:
        return

    for cur_node in ast.iter_child_nodes(node):
        if type(cur_node) == ast.FunctionDef:
            walk_method_calls(cur_node, cur_node.name)

# we pass <string> as a recognizable identifier since
# we have no filename
ast_root = compile(source, "<string>", "exec", ast.PyCF_ONLY_AST)
walk_function_defs(ast_root)

我有点怀疑这是可能的,因为
bar
在运行时之前无法解决。如果您有另一个函数:
defbaz():全局栏;bar=3
在同一模块中?然后,如果调用了
qux
bar
在调用
foo
时甚至不是一个函数,你会得到一个异常OK,我不确定这是否可行。然而,像pylint这样的静态分析器不是做了类似的事情来查看方法是否得到了解决吗?我只是在胡思乱想。我只是想看看我是否能最终编写一个脚本,打印出给定函数(纯Python)可能引发的所有异常。你可以通过运行程序来实现这一点,或者你可以尝试分析程序来检查可能会重新绑定名称的代码,或者,您可以编写自己的计算器,该计算器不采用任何巧妙的技巧,并尝试静态解析所有引用。所有这些都不是一个超级简单的下午黑客。@durden2.0--您可能能够遍历
foo.func\u code.co\u名称
或类似的名称,并尝试在假定的全局命名空间
foo
中找到具有相同名称的可调用内容--漂洗并重复;)@Marcin你说运行这个程序是什么意思?对于我的异常示例,我必须执行代码并强制它引发所有可能的异常,以便正确地看到它们?如果是这样的话,如果我试图编写一个程序来查找所有的异常,我就不能编写一个程序来引发所有的异常。也许我很困惑;)
$ python abstract_tree.py 
Found a call to bar in body of foo.
Found a call to baz in body of bar.