3

関数/メソッドについて内省したいと思っています。私のすべての例では、Python 2.7 を使用していますが、3.3 を使用しても問題はありません。

foob​​ar.py というモジュールに次のコードがあるとします。

def foo():
    bar()

実行中の foo のコードを動的に確認できます。

import inspect
import foobar
inspect.getsource(foobar.foo)

この関数のコード オブジェクトから逆アセンブルされたバイトコードを取得することもできます。

import dis
dis.dis(foobar.foo)

fooメソッドが別の関数 (barこの場合) を呼び出していることを検出し、それを動的に逆アセンブル/検査する方法はありますか?

コード オブジェクト自体には、次のようなあらゆる種類の属性があることがわかっています。

>>> 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 コードに取り掛かると、inspectおよびモジュールが役に立たないこともわかっています。最終的には、どの CPython コードにいつ到達し、分解するdisかを示すある種のマッピングを出力するのが楽しいかもしれません。ただし、それが可能かどうかさえわかりません。inspectdis

4

1 に答える 1

2

すべてのコンパイラ/インタープリターは、ソースを解析するときに抽象構文ツリーを構築します。これは、文脈自由文法に基づくプログラムの表現であり、マシンで実行できるコードを生成するために再帰的にたどることができます。

Python はその AST へのアクセスを提供し、このツリーを自分で調べてast.Call内のオブジェクトを探すことができますast.FunctionDef。簡単な例を以下に貼り付けます。ただし、呼び出しは他の式に埋め込まれたり、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)

そして実行例:

$ python abstract_tree.py 
Found a call to bar in body of foo.
Found a call to baz in body of bar.
于 2012-12-08T16:24:42.050 に答える