如何在被调用的方法中获取调用者的方法名称?

zs2*_*020 166 python introspection

Python:如何在被调用的方法中获取调用者的方法名?

假设我有两种方法:

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

def method2(self):
    ...
Run Code Online (Sandbox Code Playgroud)

如果我不想对method1进行任何更改,如何在method2中获取调用者的名称(在此示例中,名称为method1)?

Ale*_*lli 209

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
Run Code Online (Sandbox Code Playgroud)

这种内省旨在帮助调试和开发; 不建议将其用于生产功能目的.

  • @beltsonata它依赖于CPython实现,因此如果您尝试使用PyPy或Jython或其他运行时,使用它的代码将会中断.如果您只是在本地开发和调试,但在生产系统中并不是您想要的东西,那就没关系. (22认同)
  • "为了生产功能的目的,依靠它是不可取的." 为什么不? (16认同)
  • 很遗憾,这对性能造成了如此大的打击。使用这样的东西(或 [CallerMemberName](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute?view=netframework-4.8)),日志记录可能会好得多。 (3认同)
  • 嗨,大家好!毕竟.它还没有准备好生产吗? (2认同)
  • @EugeneKrevenets 除了 python 版本之外,我刚刚遇到了一个问题,它使代码在引入后在几分钟内第二次运行。效率极低 (2认同)
  • 为什么python3文档中没有提到这一点?https://docs.python.org/3/library/inspect.html 如果情况不好,他们至少会发出警告,对吗? (2认同)

Tod*_*wen 83

更短的版本:

import inspect

def f1(): f2()

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

f1()
Run Code Online (Sandbox Code Playgroud)

(感谢@Alex和Stefaan Lippen)


Aug*_*wan 53

这似乎工作得很好:

import sys
print sys._getframe().f_back.f_code.co_name
Run Code Online (Sandbox Code Playgroud)

  • 它不是受保护的成员,它记录在 https://docs.python.org/3/library/sys.html#sys._getframe 中。它有下划线,因为它是特定于实现的(可能仅限 CPython)。无论如何,检查模块也使用它来获取堆栈。 (6认同)
  • 这似乎比`inspect.stack`快得多 (4认同)
  • 它仍然使用受保护的成员,通常不建议这样做,因为在“sys”模块进行一些大量重构后它可能会失败。 (4认同)
  • 有效点。感谢您将其作为此解决方案的潜在长期陷阱放在这里。 (3认同)
  • 也适用于 pyinstaller。简短而甜蜜。我将它与 co_filename (而不是 co_name )一起使用,以了解它是从哪个主程序调用的,或者知道它是否直接出于测试目的而调用。 (2认同)

ana*_*nik 25

我想出了一个稍微长一点的版本,试图建立一个完整的方法名称,包括模块和类.

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)
Run Code Online (Sandbox Code Playgroud)

  • 难以在评论中显示...在编辑器中复制粘贴此驱动代码(从内存中键入)并尝试代码的两个版本:``` import weakref class C: pass def kill(): print('Killed' ) defleaking(): caller_name() local_var = C() weakref.finalize(local_var, kill)leaking() print("Local_var must have been Killed") ``` 你应该得到: ``` Killed Local_var must have been被杀了``` 而不是:``` Local_var 一定是被杀掉的 被杀了``` (2认同)

mig*_*k35 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
Run Code Online (Sandbox Code Playgroud)

使用:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()
Run Code Online (Sandbox Code Playgroud)

输出是

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo
Run Code Online (Sandbox Code Playgroud)

  • 如果请求的堆栈深度大于实际深度,这将引发 IndexError。使用`modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]`得到模块。 (4认同)

Acu*_*nus 6

我会用inspect.currentframe().f_back.f_code.co_name。先前的任何答案都未涵盖其使用,这些答案主要是以下三种类型之一:

  • 有些使用,inspect.stack但是众所周知它太慢了
  • sys._getframe鉴于其下划线,某些用法是内部私有函数,因此不建议使用。
  • 一种使用,inspect.getouterframes(inspect.currentframe(), 2)[1][3]但目前尚不清楚[1][3]正在访问什么。
import inspect
import types
from typing import cast


def caller_name() -> str:
    """Return the calling function's name."""
    # Ref: https://stackoverflow.com/a/57712700/
    return cast(types.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()
Run Code Online (Sandbox Code Playgroud)

致谢:1313e事先发表评论以寻求答案

  • 我认为这是最好的方法,因为没有像 calframe[1][3] 这样的神奇数字可以在未来改变。 (3认同)
  • 非常好,还有一件事是它可以在 Python 2 和 3(或至少 2.7.18 和 3.9.7)中工作。 (2认同)