呼び出されたメソッドで呼び出し元のメソッド名を取得する方法は?


187

Python:呼び出されたメソッドで呼び出し元のメソッド名を取得する方法は?

私は2つの方法があると仮定します:

def method1(self):
    ...
    a = A.method2()

def method2(self):
    ...

method1を変更したくない場合、method2で呼び出し元の名前(この例では、名前はmethod1)を取得する方法を教えてください。


3
はい。今、私はいくつかのドキュメントのものを生成し、テストのみをしたいだけです。
zs2020 2010

テクノロジーと方法論は別のものです。
zs2020 2010

回答:


233

inspect.getframeinfoおよびその他の関連する関数は、次のinspectことに役立ちます。

>>> import inspect
>>> def f1(): f2()
... 
>>> def f2():
...   curframe = inspect.currentframe()
...   calframe = inspect.getouterframes(curframe, 2)
...   print('caller name:', calframe[1][3])
... 
>>> f1()
caller name: f1

このイントロスペクションは、デバッグと開発を支援することを目的としています。運用機能の目的でこれに依存することはお勧めできません。


18
「生産機能の目的でこれに依存することはお勧めできません。」何故なの?
Beltsonata 2014

25
@beltsonataはCPythonの実装に依存しているため、PyPyやJython、またはその他のランタイムを使用しようとすると、これを使用するコードは機能しなくなります。ローカルで開発およびデバッグしているだけで問題はありませんが、本番システムで必要なものではありません。
robru

@EugeneKrevenets pythonバージョンだけでなく、導入後に数分で2回目の実行で実行されるコードを作成するという問題に遭遇しました。それは非常に非効率的です
Dmitry

なぜこれがpython3のドキュメントに記載されていないのですか?docs.python.org/3/library/inspect.html悪い場合は、少なくとも警告を発しますか?

1
これがパフォーマンスへの大きな影響であるのは残念です。ロギングは、このようなもの(またはCallerMemberName)の方がはるかに優れている場合があります。
StingyJack

92

短いバージョン:

import inspect

def f1(): f2()

def f2():
    print 'caller name:', inspect.stack()[1][3]

f1()

(@Alexのおかげで、とStefaan Lippen


こんにちは、私がこれを実行すると、以下のエラーが発生します。 <> ':IndexError:文字列インデックスが範囲外です提案を提供できますか?よろしくお願いします。
Pooja

このアプローチでエラーが発生しました:KeyError: ' main '
Praxiteles

61

これはうまくいくようです:

import sys
print sys._getframe().f_back.f_code.co_name

1
これは、inspect.stack
ケントウェイトの

それでも、保護されたメンバーを使用しsysますが、モジュールが重いリファクタリングを取得した後に失敗する可能性があるため、通常は推奨されません。
filiprem

29

モジュールとクラスを含む完全なメソッド名を作成しようとする少し長いバージョンを考え出しました。

https://gist.github.com/2151727(rev 9cccbf)

# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2

import inspect

def caller_name(skip=2):
    """Get a name of a caller in the format module.class.method

       `skip` specifies how many levels of stack to skip while getting caller
       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.

       An empty string is returned if skipped levels exceed stack height
    """
    stack = inspect.stack()
    start = 0 + skip
    if len(stack) < start + 1:
      return ''
    parentframe = stack[start][0]    

    name = []
    module = inspect.getmodule(parentframe)
    # `modname` can be None when frame is executed directly in console
    # TODO(techtonik): consider using __main__
    if module:
        name.append(module.__name__)
    # detect classname
    if 'self' in parentframe.f_locals:
        # I don't know any way to detect call from the object method
        # XXX: there seems to be no way to detect static method call - it will
        #      be just a function call
        name.append(parentframe.f_locals['self'].__class__.__name__)
    codename = parentframe.f_code.co_name
    if codename != '<module>':  # top level usually
        name.append( codename ) # function or a method

    ## Avoid circular refs and frame leaks
    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack
    del parentframe, stack

    return ".".join(name)

すごい、これは私の多くの異なる場所から呼び出すことができる私のロギングコードでうまく機能しました。どうもありがとう。
little_birdie 2015

1
またstack、削除しない限り、inspect-docsで
ankostis

@ankostisそれを証明するためのテストコードはありますか?
anatly techtonik

1
コメントで表示するのは難しい...エディターにこの駆動コード(メモリから入力)をコピーして貼り付け、両方のバージョンのコードを試してください: `` `import weakref class C:pass def kill():print( 'Killed' )def Leaking():caller_name()local_var = C()weakref.finalize(local_var、kill)leaking()print( "Local_var must be killed" ")` ``取得する必要があります: `` `Killed Local_varはkilled `` `ではなく:` `` Local_varがkilledされたはずです `` `
ankostis

1
驚くばかり!他のソリューションが失敗したときにそれはうまくいきました!私はクラスメソッドとラムダ式を使用しているため、注意が必要です。
osa

17

私は使用しますinspect.currentframe().f_back.f_code.co_name。その使用は、主に次の3つのタイプのいずれかである以前の回答のいずれにも含まれていません。

  • 以前の回答の一部は使用してinspect.stackいますが、すぎることがわかっています。
  • 一部の以前の回答sys._getframeでは、先頭のアンダースコアを考慮して内部プライベート関数を使用しているため、暗黙的に使用することはお勧めしません。
  • 以前の回答の1つを使用してinspect.getouterframes(inspect.currentframe(), 2)[1][3]いますが、何[1][3]にアクセスしているかは完全に不明です。
import inspect
from types import FrameType
from typing import cast


def caller_name() -> str:
    """Return the calling function's name."""
    # Ref: https://stackoverflow.com/a/57712700/
    return cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name


if __name__ == '__main__':
    def _test_caller_name() -> None:
        assert caller_name() == '_test_caller_name'
    _test_caller_name()

cast(FrameType, frame)がを満たすために使用されることに注意してくださいmypy


謝辞:1313eによる回答の事前コメント。


10

上記のものの融合のビット。しかし、ここに私の亀裂があります。

def print_caller_name(stack_size=3):
    def wrapper(fn):
        def inner(*args, **kwargs):
            import inspect
            stack = inspect.stack()

            modules = [(index, inspect.getmodule(stack[index][0]))
                       for index in reversed(range(1, stack_size))]
            module_name_lengths = [len(module.__name__)
                                   for _, module in modules]

            s = '{index:>5} : {module:^%i} : {name}' % (max(module_name_lengths) + 4)
            callers = ['',
                       s.format(index='level', module='module', name='name'),
                       '-' * 50]

            for index, module in modules:
                callers.append(s.format(index=index,
                                        module=module.__name__,
                                        name=stack[index][3]))

            callers.append(s.format(index=0,
                                    module=fn.__module__,
                                    name=fn.__name__))
            callers.append('')
            print('\n'.join(callers))

            fn(*args, **kwargs)
        return inner
    return wrapper

使用する:

@print_caller_name(4)
def foo():
    return 'foobar'

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

出力は

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo

2
要求されたスタックの深さが実際よりも大きい場合、これによりIndexErrorが発生します。modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]モジュールを取得するために使用します。
jake77 2018

1

私はあなたがクラスを越えて行き、メソッドが属しているクラスとメソッドが欲しい場合に方法を見つけました。それは少し抽出作業を必要としますが、それはその要点を明らかにします。これはPython 2.7.13で動作します。

import inspect, os

class ClassOne:
    def method1(self):
        classtwoObj.method2()

class ClassTwo:
    def method2(self):
        curframe = inspect.currentframe()
        calframe = inspect.getouterframes(curframe, 4)
        print '\nI was called from', calframe[1][3], \
        'in', calframe[1][4][0][6: -2]

# create objects to access class methods
classoneObj = ClassOne()
classtwoObj = ClassTwo()

# start the program
os.system('cls')
classoneObj.method1()

0
#!/usr/bin/env python
import inspect

called=lambda: inspect.stack()[1][3]

def caller1():
    print "inside: ",called()

def caller2():
    print "inside: ",called()

if __name__=='__main__':
    caller1()
    caller2()
shahid@shahid-VirtualBox:~/Documents$ python test_func.py 
inside:  caller1
inside:  caller2
shahid@shahid-VirtualBox:~/Documents$
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.