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

Posted

技术标签:

【中文标题】如何在被调用的方法中获取调用者的方法名?【英文标题】:How to get the caller's method name in the called method? 【发布时间】:2011-02-08 21:26:24 【问题描述】:

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

假设我有两种方法:

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

def method2(self):
    ...

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

【问题讨论】:

Getting the caller function name inside another function in Python?的可能重复 【参考方案1】:

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

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

【讨论】:

“不建议将其用于生产功能目的。”为什么不呢? @beltsonata 它依赖于 CPython 实现,因此如果您尝试使用 PyPy 或 Jython 或其他运行时,使用它的代码将会中断。如果您只是在本地进行开发和调试,而不是在生产系统中真正想要的东西,那很好。 @EugeneKrevenets 除了 python 版本之外,我还遇到了一个问题,它使代码在引入后几分钟内就可以在第二次运行下运行。它效率极低 为什么python3文档中没有提到这个? docs.python.org/3/library/inspect.html 如果情况不好,他们至少会发出警告吧? 很遗憾,这对性能造成了如此大的影响。使用这样的东西(或CallerMemberName),日志记录会更好。【参考方案2】:

短版:

import inspect

def f1(): f2()

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

f1()

(感谢@Alex 和Stefaan Lippen)

【讨论】:

您好,当我运行此程序时出现以下错误:文件“/usr/lib/python2.7/inspect.py”,第 528 行,在 findsource 中,如果不是源文件和文件 [0] + file[-1] != '': IndexError: string index out of range 你能提供建议吗?提前致谢。 这种方法给了我一个错误:KeyError: 'main' 在 Python 3 中你需要: print('caller name:', inspect.stack()[1][3])【参考方案3】:

这似乎工作得很好:

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

【讨论】:

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

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

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)

【讨论】:

太棒了,这在我的日志记录代码中效果很好,可以从很多不同的地方调用我。非常感谢。 除非你也删除stack,否则它仍然会由于循环引用而泄漏帧,如inspect-docs中所述 @ankostis 你有一些测试代码来证明这一点吗? 难以在评论中显示...在编辑器中复制粘贴此驱动代码(从内存中键入)并尝试两个版本的代码:``` 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 have been deleted") ``` 你应该得到: ``` 已杀死 Local_var 必须已被杀死 ``` 而不是:``` Local_var 必须已被杀死已被杀死 ``` 太棒了!当其他解决方案失败时,这有效!我正在使用类方法和 lambda 表达式,所以这很棘手。【参考方案5】:

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

之前的一些答案使用inspect.stack,但众所周知它也使用slow。 之前的一些答案使用sys._getframe,它是一个内部私有函数,因为它的前导下划线,因此不鼓励使用它。 之前的一个答案使用 inspect.getouterframes(inspect.currentframe(), 2)[1][3],但完全不清楚 [1][3] 正在访问什么。
import inspect
from types import FrameType
from typing import cast


def demo_the_caller_name() -> str:
    """Return the calling function's name."""
    # Ref: https://***.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 demo_the_caller_name() == '_test_caller_name'
    _test_caller_name()

注意cast(FrameType, frame)是用来满足mypy的。


致谢:1313e 评论answer。

【讨论】:

我想知道如果直接调用该函数,调用者名称是什么。打印它会得到&lt;module&gt;。每当函数直接按名称调用时,总是会出现这种情况吗?或者有没有会弹出模块名的情况? @VahagnTumanyan 如果您从模块(从任何函数外部)调用它,它会说出您所看到的。但是,此答案不会显示模块名称。您可以从函数中调用它以查看调用函数的名称。 我猜这是最好的方法,因为没有像 calframe[1][3] 这样的神奇数字可以在未来改变。 非常好,还有一个好处是它可以在 Python 2 和 3(或至少 2.7.18 和 3.9.7)中工作。【参考方案6】:

上面的东西有点融合。但这是我的破解之道。

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

【讨论】:

如果请求的堆栈深度大于实际堆栈深度,这将引发 IndexError。使用modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))] 获取模块。 非常感谢。我建议收集模块有点不同,又名:modules = [(index, module) for index in reversed(range(1, min(stack_size, len(stack)))) if index and (module := inspect.getmodule(stack[index][0]))]。这样,您可以确保在您的进一步列表中不会有 Nones。另外我认为添加module_name_lengths.append(len(func.__module__)) 很好,因为格式没有考虑表格中最低的元素。 在这个解决方案中,我们也应该return fn(*args, **kwargs),因为当前的解决方案会抑制装饰函数的返回并破坏它们。【参考方案7】:

如果您要跨类并想要方法所属的类和方法,我找到了一种方法。这需要一些提取工作,但它说明了它的意义。这适用于 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()

【讨论】:

【参考方案8】:

嘿,伙计,我曾经为我的应用制作了 3 种没有插件的方法,也许这可以帮助你,它对我有用,所以也许对你也有用。

def method_1(a=""):
    if a == "method_2":
        print("method_2")

    if a == "method_3":
        print("method_3")


def method_2():
    method_1("method_2")


def method_3():
    method_1("method_3")


method_2()

【讨论】:

【参考方案9】:

代码:

#!/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$

【讨论】:

called() 是被调用的函数,但不是调用 callerN() 的函数

以上是关于如何在被调用的方法中获取调用者的方法名?的主要内容,如果未能解决你的问题,请参考以下文章

java 获取调用类的类名和方法名

Java中的方法参数如何接收调用者的值

Java中的方法参数如何接收调用者的值

Java中的方法参数如何接收调用者的值

Java运行时获取当前运行代码类名方法名

oracle如何获取调用者的IP,机器名等信息,以及他们操作的SQL