ホーム>source

別のメソッド/関数内で呼び出されたメソッド/関数のリストを一覧表示できるツール/ライブラリはありますか?

例えば: そのツールまたはライブラリが以下の方法で実行される場合

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

この質問はほぼ同じですこれです しかし、それはJava用です。

これは基本的に PyCharm と同じです   Find Usage の場合 。 ありがとう!

あなたの答え
  • 解決した方法 # 1

    これは仕事をしているようです:

    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']
    
    

    ここで何が起こっていますか:

    関数のBytecodeオブジェクトを作成します

    関数名は 関数呼び出しに従う

    リストをステップスルーし、CALL_FUNCTION命令ごとに、

    指示 arg を使用します  パラメータの数 私たちが得ている引数

    関数をロードする命令を見つけるために過去を1つ調べます 私たちは呼んでいます

    その関数の名前( instr.argval )次にリストに リバース、列挙、および要求された形式で返す

    Python 3.6以降、3つの CALL_FUNCTION があることに注意してください  そのため、現在のpythonで完全に機能するようにこの例を拡張するには、ドキュメントを確認する必要があります

  • 解決した方法 # 2

    更新:  Python2.7 の互換性を追加
    Python2.7 でのテストと動作確認 、 Python3.5  および Python3.6


    dis を指摘した功績  パトリック・ハウ¹に行く 
    実装( 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']
    
    

    ¹パトリック・ハウの dis に関するコメントとして  私はこれを検討します撮影は無料...

関連記事

  • 前へ java - JPAクエリ:サブクエリをグループ化条件に結合する
  • 次へ mysql - 2番目のテーブルから最後に一致した結果を持つ結合テーブルを返す方法は?