7

関数レベルでpythonパッケージの関数と変数の使用/原因をマッピングしようとしています。関数/変数が他の関数で使用されるモジュールがいくつかあり、次のような辞書を作成したいと思います。

{'function_name':{'uses': [...functions used in this function...],
                  'causes': [...functions that use this function...]},
 ...
}

私が参照している関数は、パッケージのモジュールで定義する必要があります。

これをどのように開始しますか?__dict__次のようにして、パッケージを反復処理し、パッケージで定義されている関数をテストできることを知っています。

import package

import inspect
import types

for name, obj in vars(package).items():
    if isinstance(obj, types.FunctionType):
        module, *_ = inspect.getmodule(obj).__name__.split('.')
        if module == package.__name__:
            # Now that function is obtained need to find usages or functions used within it

しかしその後、現在の関数内で使用されている関数を見つける必要があります。これはどのように行うことができますか?このタイプの作業用にすでに開発されたものはありますか? プロファイリング ライブラリは、これと同様のことを行う必要があるかもしれないと思います。

4

1 に答える 1

2

コメントで提案されているastモジュールは、うまく機能しました。これは、各関数で使用されるパッケージで定義された関数または変数を抽出するために使用される、私が作成したクラスです。

import ast
import types
import inspect


class CausalBuilder(ast.NodeVisitor):

    def __init__(self, package):
        self.forest = []
        self.fnames = []

        for name, obj in vars(package).items():
            if isinstance(obj, types.ModuleType):
                with open(obj.__file__) as f:
                    text = f.read()
                tree = ast.parse(text)
                self.forest.append(tree)
            elif isinstance(obj, types.FunctionType):
                mod, *_ = inspect.getmodule(obj).__name__.split('.')
                if mod == package.__name__:
                    self.fnames.append(name)

        self.causes = {n: [] for n in self.fnames}

    def build(self):
        for tree in self.forest:
            self.visit(tree)
        return self.causes

    def visit_FunctionDef(self, node):
        self.generic_visit(node)
        for b in node.body:
            if node.name in self.fnames:
                self.causes[node.name] += self.extract_cause(b)

    def extract_cause(self, node):
        nodes = [node]
        cause = []
        while nodes:
            for i, n in enumerate(nodes):
                ntype = type(n)
                if ntype == ast.Name:
                    if n.id in self.fnames:
                        cause.append(n.id)
                elif ntype in (ast.Assign, ast.AugAssign, ast.Attribute,
                               ast.Subscript, ast.Return):
                    nodes.append(n.value)
                elif ntype in (ast.If, ast.IfExp):
                    nodes.append(n.test)
                    nodes.extend(n.body)
                    nodes.extend(n.orelse)
                elif ntype == ast.Compare:
                    nodes.append(n.left)
                    nodes.extend(n.comparators)
                elif ntype == ast.Call:
                    nodes.append(n.func)
                elif ntype == ast.BinOp:
                    nodes.append(n.left)
                    nodes.append(n.right)
                elif ntype == ast.UnaryOp:
                    nodes.append(n.operand)
                elif ntype == ast.BoolOp:
                    nodes.extend(n.values)
                elif ntype == ast.Num:
                    pass
                else:
                    raise TypeError("Node type `{}` not accounted for."
                                    .format(ntype))

                nodes.pop(nodes.index(n))

        return cause

このクラスは、最初に python パッケージをインポートしてコンストラクターに渡し、次に次のようにbuildメソッドを呼び出すことで使用できます。

import package

cb = CausalBuilder(package)
print(cb.build())

これは、関数の名前を表す一連のキーと、関数や関数で使用される変数を示すリストである値を含む辞書を出力します。すべての ast タイプが考慮されているわけではありませんが、私の場合はこれで十分でした。

ast.Name実装は、ターゲット関数内で使用されている変数、関数、またはメソッドの名前を抽出できるようになるまで、ノードをより単純な型に再帰的に分解します。

于 2016-12-22T20:49:40.847 に答える