在函数内部确定函数名称(不使用traceback)

733

在Python中,不使用traceback模块,有没有一种方法可以确定一个函数在函数内部的名称?

假设我有一个名为foo的模块和一个名为bar的函数。当执行foo.bar()时,是否有一种方法让bar知道自己的名称?或者更好的是,知道foo.bar的名称?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?

如果你对追踪打印语句的来源感兴趣,还可以查看使用日志记录包的解决方案:https://stackoverflow.com/a/20112491/8305404 - undefined
27个回答

574
import inspect

def foo():
   print(inspect.stack()[0][3])
   print(inspect.stack()[1][3])  # will give the caller of foos name, if something called foo

foo()

输出:

foo
<module_caller_of_foo>

107
您也可以使用以下代码来获取当前函数的名称:print(inspect.currentframe().f_code.co_name),或者用以下代码获取调用者的名称:print(inspect.currentframe().f_back.f_code.co_name)。我认为这样会更快,因为它不需要像inspect.stack()一样检索整个堆栈帧列表。 - Michael
15
使用装饰过的方法时,inspect.currentframe().f_back.f_code.co_name无效,而inspect.stack()[0][3]可行。 - Dustin Wyatt
8
请注意:inspect.stack() 可能会造成严重的性能损耗,所以请谨慎使用!在我的 ARM 设备上,它需要 240 毫秒才能完成(原因不详)。 - gardarh
3
@Michael,请将您的评论发布为答案。 - Давид Шико
3
代码中的魔数 3 是什么意思? - tommy.carstensen
显示剩余2条评论

282

Python没有一种方法可以在函数本身内部访问函数或其名称。虽然已经有提案(PEP 3130),但被拒绝了。如果你不想自己修改堆栈,你应该根据上下文使用"bar"bar.__name__

关于该提案的拒绝通知如下:

该PEP被拒绝。目前尚不清楚它应该如何实现,以及在边缘情况下应该具有什么精确语义,而且重要的用例不够多。反应最多只是温热的。


31
inspect.currentframe()是其中一种方法。 - Yuval
70
将@CamHart的方法与@Yuval的结合,可以避免@RoshOxymoron答案中的“隐藏”和可能过时的方法以及@neuro/@AndreasJung答案中对堆栈进行数值索引的问题:print(inspect.currentframe().f_code.co_name) - hobs
3
为什么被拒绝了,能否简述一下原因? - Charlie Parker
10
为什么这个被选为答案?问题不是关于访问当前函数或模块本身,只是关于名称。而堆栈跟踪/调试功能已经有了这个信息。 - nurettin
6
这个回答太古老了,已经不正确了。请将其删除。 - Florian
显示剩余2条评论

276

有几种方法可以获得相同的结果:

import sys
import inspect

def what_is_my_name():
    print(inspect.stack()[0][0].f_code.co_name)
    print(inspect.stack()[0][3])
    print(inspect.currentframe().f_code.co_name)
    print(sys._getframe().f_code.co_name)

请注意,inspect.stack 调用比其它替代方法慢数千倍:

$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][0].f_code.co_name'
1000 loops, best of 3: 499 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][3]'
1000 loops, best of 3: 497 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.currentframe().f_code.co_name'
10000000 loops, best of 3: 0.1 usec per loop
$ python -m timeit -s 'import inspect, sys' 'sys._getframe().f_code.co_name'
10000000 loops, best of 3: 0.135 usec per loop

更新于2021年08月(原始帖子是针对Python2.7编写的)

Python 3.9.1 (default, Dec 11 2020, 14:32:07)
[GCC 7.3.0] :: Anaconda, Inc. on linux

python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][0].f_code.co_name'
500 loops, best of 5: 390 usec per loop
python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][3]'
500 loops, best of 5: 398 usec per loop
python -m timeit -s 'import inspect, sys' 'inspect.currentframe().f_code.co_name'
2000000 loops, best of 5: 176 nsec per loop
python -m timeit -s 'import inspect, sys' 'sys._getframe().f_code.co_name'
5000000 loops, best of 5: 62.8 nsec per loop

14
inspect.currentframe() 看起来是执行时间和使用私有成员之间的一个很好的权衡。 - FabienAndre
1
@mbdevpl 我的数字分别是1.25毫秒,1.24毫秒,0.5微秒和0.16微秒正常(非Pythonic :))秒(win7x64,python3.5.1)。 - Antony Hatchkins
6
我使用 sys._getframe().f_code.co_name 而非 inspect.currentframe().f_code.co_name,仅因为我已经导入了 sys 模块。考虑到速度看起来相当接近,这是一个合理的决定吗? - PatrickT
6
这是完整的答案,并且在我看来应该被接受。 - Nam G VU
3
我们可以使用 sys._getframe().f_back.f_code.co_name 来获取调用者的名称。 - Fernando Crespo
显示剩余8条评论

73
functionNameAsString = sys._getframe().f_code.co_name

我想要一个非常相似的东西,因为我想将函数名放入一个在我的代码中出现多次的日志字符串中。这可能不是最好的方法,但这是获取当前函数名称的一种方式。


3
完全可行,只需使用sys,无需加载更多模块,但不太容易记住它 :V - m3nda
日志记录支持在消息格式字符串中包含函数名称。 - Gringo Suave
_ 表示私有方法。请不要公开调用它们。您可能想使用inspect.currentframe(),它是一个公共方法。 - Roland
_ 表示一个私有方法。请不要公开调用它们。你可能想要使用 inspect.currentframe(),这是一个公共方法。 - undefined
罗兰,你可能有一定道理,但看看这个答案得到了多少点赞。即使是我也更愿意使用这个答案而不是 inspect,正如一个名叫gardah的评论(在上面的一个回答中)所说,它需要很长时间。如果问题是因为它是私有的,那么就需要说服开发人员提供一个名为 getCurrentFunctionName()getParentFunctionName() 的公共函数。 - Nav

55

您可以使用@Andreas Jung所示的方法获取函数定义时的名称,但这可能不是调用函数时使用的名称:

import inspect

def Foo():
   print inspect.stack()[0][3]

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo
无法确定该区分对您是否重要。

4
.func_name一样的情况。值得记住,在Python中,类名和函数名是一回事,而引用它们的变量是另一回事。 - Kos
有时您可能希望 Foo2() 打印 Foo。例如:Foo2 = function_dict['Foo']; Foo2()。在这种情况下,Foo2 是一个函数指针,用于命令行解析器。 - Harvey
这会有什么速度影响? - Robert C. Barth
3
速度涉及到什么?是否存在需要在硬实时情况下获取此信息的情况? - bgporter

37

我将这个方便的工具放在附近:

import inspect
myself = lambda: inspect.stack()[1][3]

使用:

myself()

1
用这里提出的替代方案怎么做呢? "myself = lambda: sys._getframe().f_code.co_name" 不起作用(输出为“<lambda>”;我认为是因为结果在定义时确定,而不是在调用时确定。嗯。 - NYCeyes
2
@prismalytics.io:如果你调用myself()而不仅仅使用它的值(myself),你将得到你想要的结果。 - ijw
1
NYCeyes是正确的,名称在lambda内部解析,因此结果为<lambda>。sys._getframe()和inspect.currentframe()方法必须直接在您想要获取名称的函数内执行。inspect.stack()方法有效是因为您可以指定索引1,执行inspect.stack()[0][3]也会产生<lambda>。 - kikones34
请勿使用匿名函数字面量(lambda...)创建命名函数。请使用def正常定义函数,避免可能出现的多种问题。 - pabouk - Ukraine stay strong

31

我猜 inspect 是最好的方法来实现这个目标。例如:

import inspect
def bar():
    print("My name is", inspect.stack()[0][3])

8
使用inspect.stack()[0].function代替inspect.stack()[0][3],即使堆栈追踪中的语义发生变化,该方法应该更加健壮。 - Tom Pohl

30

这实际上是从其他回答问题的答案中得出的。

以下是我的看法:

import sys

# for current func name, specify 0 or no argument.
# for name of caller of current func, specify 1.
# for name of caller of caller of current func, specify 2. etc.
currentFuncName = lambda n=0: sys._getframe(n + 1).f_code.co_name


def testFunction():
    print "You are in function:", currentFuncName()
    print "This function's caller was:", currentFuncName(1)    


def invokeTest():
    testFunction()


invokeTest()

# end of file

使用sys._getframe()相比使用inspect.stack()的明显优势在于它应该快上数千倍 [详见Alex Melihoff关于使用sys._getframe()与使用inspect.stack()时序的帖子]。


适用于Python 3.7。 - Sinux1

29

我发现了一个包装器,它可以写入函数名称

from functools import wraps

def tmp_wrap(func):
    @wraps(func)
    def tmp(*args, **kwargs):
        print func.__name__
        return func(*args, **kwargs)
    return tmp

@tmp_wrap
def my_funky_name():
    print "STUB"

my_funky_name()

这将打印

我的花里胡哨的名字

STUB


1
作为一个装饰器新手,我想知道是否有一种方法可以在 my_funky_name 的上下文中访问 func.__name__(这样我就可以检索它的值并在 my_funky_name 中使用它)。 - cowbert
my_funky_name 函数内部执行此操作的方式是 my_funky_name.__name__。您可以将 func.__name__ 作为新参数传递到该函数中。func(*args, **kwargs, my_name=func.__name__)。要从函数内部获取装饰器的名称,我认为需要使用 inspect。但从运行函数控制我的函数的名称,听起来就像是一个美丽的梗的开端 :) - cad106uk

22

我不确定为什么人们会把它弄得很复杂:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))

3
也许是因为你在 sys 模块的私有函数之外使用了它。一般来说,这被认为是一种不好的做法,不是吗? - tikej
3
@tikej,使用方法遵循此处所述的最佳实践:https://docs.quantifiedcode.com/python-anti-patterns/correctness/accessing_a_protected_member_from_outside_the_class.html - karthik r

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接