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

2024-12-27 08:47:00
admin
原创
116
摘要:问题描述:Python:如何在被调用的方法中获取调用者的方法名称?假设我有两种方法:def method1(self): ... a = A.method2() def method2(self): ... 如果我不想对method1做任何改变,那么如何在method2中获取调用者的...

问题描述:

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

假设我有两种方法:

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

def method2(self):
    ...

如果我不想对method1做任何改变,那么如何在method2中获取调用者的名称(在这个例子中,名称是method1)?


解决方案 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

这种自省旨在帮助调试和开发;不建议依赖它来实现生产功能。

解决方案 2:

简短版本:

import inspect

def f1(): f2()

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

f1()

(感谢@Alex 和Stefaan Lippen)

解决方案 3:

这似乎工作得很好:

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

解决方案 4:

我会用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 demo_the_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 demo_the_caller_name() == '_test_caller_name'
    _test_caller_name()

注意cast(FrameType, frame)用于满足mypy


致谢:1313e 的评论以获得答案。

解决方案 5:

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

https://gist.github.com/2151727(修订版 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)

解决方案 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('
'.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

解决方案 7:

sys._getframe()Python 的 Logging 库使用 的替代方法来查找调用者信息。思路如下:

  1. 引发异常

  2. 立即在 Except 子句中捕获它

  3. 用于sys.exc_info获取回溯框架(tb_frame)。

  4. 使用获取上tb_frame一个呼叫者的框架f_back

  5. 从最后一个调用者的框架中获取在该框架中正在执行的代码对象。

在我们的示例代码中它将method1(不会method2)被执行。

  1. 从获取的代码对象中获取对象的名称——这是我们示例中调用者方法的名称。

以下是解决问题的示例代码:

def method1():
    method2()

def method2():
    try:
        raise Exception
    except Exception:
        frame = sys.exc_info()[2].tb_frame.f_back

    print("method2 invoked by: ", frame.f_code.co_name)

# Invoking method1
method1()

输出:

method2 invoked by: method1

框架包含各种详细信息,包括行号、文件名、参数计数、参数类型等。该解决方案也适用于类和模块。

解决方案 8:

您可以使用装饰器,而不必使用堆栈跟踪

如果你想装饰类中的一个方法

import functools

# outside ur class
def printOuterFunctionName(func):
@functools.wraps(func)
def wrapper(self):
    print(f'Function Name is: {func.__name__}')
    func(self)    
return wrapper 

class A:
  @printOuterFunctionName
  def foo():
    pass

如果是程序性的functools,你可以删除self

解决方案 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$

解决方案 10:

对于仅打印到模块名称之前的多级调用,以下函数是一个简单的解决方案:

import inspect

def caller_name():
    frames = inspect.stack()
    caller_name = ''
    for f in frames:
        if f.function == '<module>':
            return caller_name
        caller_name = f.function

def a():
    caller = caller_name()
    print(f"'a' was called from '{caller}'")

def b():
    a()

def c():
    b()

c()

输出:

'a' was called from 'c'

解决方案 11:

Python 在 3.11 中对其进行了改进,增加了codeobject.co_qualname,它返回函数的完全限定名称。

如果您正在使用上面@Asclepius 的解决方案,请考虑这样做,因为它还包括定义方法的类名:

import inspect

def who_is_calling():
    print(inspect.currentframe().f_back.f_code.co_qualname)
    

class Klass:
    def method(self): 
        who_is_calling()      # prints 'Klass.method'

    @classmethod
    def class_method(cls): 
        who_is_calling()      # prints 'Klass.class_method'

    @staticmethod
    def static_method():
        who_is_calling()      # prints 'Klass.static_method'

    who_is_calling()          # prints 'Klass'

    class InnerKlass:
        def inner_method(self): 
            who_is_calling()  # prints 'Klass.InnerKlass.inner_method'


def unbound_function(): 
    who_is_calling()          # prints 'unbound_function'

这有什么好处?它适用于任何函数、方法、类方法、静态方法……甚至类本身(和嵌套类)!

解决方案 12:

如果您要跨类查找方法,并希望找到该方法所属的类和该方法,我找到了一种方法。这需要一点提取工作,但它很有意义。这在 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 '
I was called from', calframe[1][3], \n        '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()

解决方案 13:

这是一个使用@user47 的答案的便捷方法。

def trace_caller():
    try:
        raise Exception
    except Exception:
        frame = sys.exc_info()[2].tb_frame.f_back.f_back
        print(" >> invoked by:", frame.f_code.co_name)

解决方案 14:

以下是获取调用方法及其类的方法:

import sys
frame = sys._getframe().f_back
calling_method = frame.f_code.co_name
calling_class = frame.f_locals.get("self").__class__.__name__
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用