如何在被调用的方法中获取调用者的方法名?
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。
【讨论】:
我想知道如果直接调用该函数,调用者名称是什么。打印它会得到<module>
。每当函数直接按名称调用时,总是会出现这种情况吗?或者有没有会弹出模块名的情况?
@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]))]
。这样,您可以确保在您的进一步列表中不会有 None
s。另外我认为添加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()
的函数以上是关于如何在被调用的方法中获取调用者的方法名?的主要内容,如果未能解决你的问题,请参考以下文章