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


182

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

假设我有2种方法:

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

def method2(self):
    ...

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


3
是。现在,我只想生成一些文档资料,并且仅用于测试。
zs2020 2010年

技术是一回事,方法论是另一回事。
zs2020's

Answers:


228

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

25
@beltsonata它依赖于CPython实现,因此,如果您尝试使用PyPy或Jython或其他运行时,使用此代码的代码将中断。如果您只是在本地进行开发和调试,而不是在生产系统中真正想要的东西,那很好。
robru 2015年

@EugeneKrevenets除了python版本以外,我还遇到了一个问题,即一旦引入,它会使代码在第二分钟内运行。它严重效率低下
德米特里(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


您好,我在运行此命令时遇到以下错误:如果不是sourcefile和file [0] + file [-1]!=',文件“ /usr/lib/python2.7/inspect.py”,第528行,在findsource中<>':IndexError:字符串索引超出范围您可以提供建议吗?提前感谢。
Pooja 2014年

这种做法给了我一个错误:KeyError异常:“
普拉克西特利斯

60

这似乎很好用:

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

1
这似乎比inspect.stack
kentwait

它仍然使用受保护的成员,通常不建议这样做,因为在sys模块进行大量重构之后,它可能会失败。
filiprem

28

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

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

1
除非您也将stack其删除,否则它仍会由于inspect docs
圆形引用

@ankostis您是否有一些测试代码来证明这一点?
anatoly techtonik

1
难以在评论中显示...在编辑器中复制并粘贴此驱动代码(从内存中键入),然后尝试两种版本的代码: )def Leaking():caller_name()local_var = C()杀```而不是:```Local_var必须被杀死杀```
ankostis

1
太棒了!当其他解决方案失败时,这种方法就起作用了!我正在使用类方法和lambda表达式,所以很棘手。
osa

16

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

  • 使用一些先前的答案,inspect.stack但已知它太慢
  • 一些先前的答案使用 sys._getframe是给定其下划线的内部私有函数,因此不建议使用它。
  • 使用一个先前的答案,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 '18

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$
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.