从该函数中确定函数名称(不使用回溯)


479

在Python中,不使用traceback模块,是否有办法从该函数内部确定函数名称?

说我有一个带功能栏的模块foo。执行时foo.bar(),bar是否有办法知道bar的名称?还是更好,foo.bar名字?

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

6
我不明白为什么接受的答案不是安德烈亚斯·扬(Andreas Young)或其他任何表明如何做到的答案。相反,公认的答案是“您不能那样做”,这似乎是错误的。OP的唯一要求是不使用traceback。甚至没有答案和评论的时代似乎也支持它。
sancho.s ReinstateMonicaCellio

Answers:


198

Python没有功能来访问函数本身或函数内部的名称。已经提出但遭到拒绝。如果您不想自己玩堆栈,则应该使用"bar"bar.__name__取决于上下文。

给定的拒绝通知是:

该PEP被拒绝。目前尚不清楚在极端情况下应该如何实现它或确切的语义,也没有足够的重要用例。回应充其量是冷淡的。


17
inspect.currentframe()就是这样一种方式。
Yuval 2014年

41
@CamHart的方法与@Yuval的方法相结合,避免了@RoshOxymoron的答案中的“隐藏”和可能不推荐使用的方法,以及@ neuro / @ AndreasJung的答案在堆栈中的数值索引:print(inspect.currentframe().f_code.co_name)
滚刀

2
是否有可能总结其被拒绝的原因?
查理·帕克

1
为什么选择这个答案?问题不在于访问当前函数或模块本身,而在于名称。并且stacktrace / debugging功能已经具有此信息。
nurettin

409
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

47
这很棒,因为您也[1][3]可以获取呼叫者的姓名。
科斯2012年

57
您还可以使用:print(inspect.currentframe().f_code.co_name)或获取呼叫者的姓名:print(inspect.currentframe().f_back.f_code.co_name)。我认为应该更快,因为您不会像这样检索所有堆栈帧的列表inspect.stack()
2014年

7
inspect.currentframe().f_back.f_code.co_name不适用于修饰的方法,而适用于inspect.stack()[0][3]
Dustin Wyatt

4
请注意:inspect.stack()会产生大量的性能开销,因此请谨慎使用!在我的手臂箱上,花了
240毫秒

在我看来,可能需要使用Python递归机制中存在的某些东西来更有效地执行此操作
Tom Russell

160

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

from __future__ import print_function
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

5
inspect.currentframe()在执行时间和使用私人成员之间似乎是一个很好的权衡
FabienAndre

1
@mbdevpl我的数字分别是正常(nonpythonic :) 1.25ms,1.24ms,0.5us,0.16us正常秒(win7x64,python3.5.1)
Antony Hatchkins

只是没有人认为@mbdevpl太疯狂了:)-我提交了第3次运行输出的编辑,因为这没有任何意义。不知道结果是否应该- 0.100 usec还是0.199 usec比选项1和2快数百倍,可与选项4相提并论(尽管Antony Hatchkins发现选项3比选项4快三倍)。
dwanderson

sys._getframe().f_code.co_name用完inspect.currentframe().f_code.co_name只是因为我已经导入了sys模块。这是一个合理的决定吗?(考虑到速度看起来非常相似)
PatrickT

47

您可以使用@Andreas Jung显示的方法来获得定义的名称,但这可能不是使用该函数调用的名称:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

我不能说这种区别对您是否重要。


3
与的情况相同.func_name。值得记住的是,Python中的类名和函数名是一回事,而引用它们的变量是另一回事。
科斯2012年

有时您可能需要Foo2()打印Foo。例如:Foo2 = function_dict['Foo']; Foo2()。在这种情况下,Foo2是可能用于命令行解析器的函数指针。
哈维

这意味着什么速度?
罗伯特·巴思

2
关于速度的含义是什么?在某种情况下,您需要在困难的实时情况下获取此信息吗?
bgporter 2014年

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

我想要一个非常类似的东西,因为我想将函数名称放在一个日志字符串中,该字符串在我的代码中占据了很多位置。可能不是执行此操作的最佳方法,但是这是一种获取当前函数名称的方法。


2
完全可以正常工作,仅使用sys即可,无需加载更多模块,但也不太容易记住它:V
m3nda

@ erm3nda查看我的答案。
nerdfever.com

1
@ nerdfever.com我的问题不是要创建一个函数,就是不记得在该函数中放什么。不容易记住,因此我将需要始终看到一些注释以再次构建该注释。我会尽量记住f框架和co代码。如果我将其保存在一些代码段中,我不会用得那么好:-)
m3nda

24

我将这个方便的实用程序放在附近:

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

NYCeyes是正确的,该名称在lambda内解析,因此结果为<lambda>。sys._getframe()和inspect.currentframe()方法必须直接在要获取名称的函数中执行。inspect.stack()方法之所以有效,是因为您可以指定索引1,执行inspect.stack()[0] [3]也会产生<lambda>。
kikones34

20

我想这inspect是最好的方法。例如:

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

17

我找到了一个将写函数名称的包装器

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()

这将打印

my_funky_name

存根


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

15

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

这是我的看法:

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

与使用inspect.stack()相比,此版本的可能优势是它应该快数千倍[请参阅Alex Melihoff的文章和有关使用sys._getframe()而不是使用inspect.stack()的时间]。



11

这是一种面向未来的方法。

将@CamHart和@Yuval的建议与@RoshOxymoron的可接受答案结合起来,可以避免以下情况:

  • _hidden 和可能不推荐使用的方法
  • 索引到堆栈中(可以在以后的python中重新排序)

因此,我认为这对将来的python版本(在2.7.3和3.3.2中进行测试)非常有用:

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))

11
import sys

def func_name():
    """
    :return: name of caller
    """
    return sys._getframe(1).f_code.co_name

class A(object):
    def __init__(self):
        pass
    def test_class_func_name(self):
        print(func_name())

def test_func_name():
    print(func_name())

测试:

a = A()
a.test_class_func_name()
test_func_name()

输出:

test_class_func_name
test_func_name


10
import inspect

def whoami():
    return inspect.stack()[1][3]

def whosdaddy():
    return inspect.stack()[2][3]

def foo():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())
    bar()

def bar():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())

foo()
bar()

在IDE中,代码输出

你好,我是foo,爸爸是

你好,我是酒吧,爸爸是foo

你好,我在酒吧,爸爸是


8

您可以使用装饰器:

def my_function(name=None):
    return name

def get_function_name(function):
    return function(name=function.__name__)

>>> get_function_name(my_function)
'my_function'

那如何回答发帖人的问题?您是否可以扩展它以包括一个示例,以了解如何从函数内部知道函数名称?
parvus

@parvus:我的答案是是是,演示了答案OP的问题的例子
道格拉斯Denhartog

好的,my_function是OP的随机用户函数。怪我对装饰工缺乏了解。@在哪里?对于不想修改其参数的函数,这将如何工作?我如何理解您的解决方案:当我想知道函数名称时,必须在其后面附加@get_function_name,并添加name参数,希望它不存在于其他用途。我可能会错过一些东西,对此感到抱歉。
parvus19年

无需在注释中开始我自己的python课程:1.函数是对象;2.您可以将名称属性附加到函数,打印/记录名称,或在装饰器中使用“名称”执行任意数量的操作;3.装饰器可以多种方式连接(例如@或在我的示例中);4.装饰者可以使用@wraps和/或自己成为类;5.我可以继续,但是编程愉快!
道格拉斯·登哈托格

这看起来像是一种复杂的方法来获取__name__函数的属性。用法需要知道您要获取的东西,这在我没有动态定义函数的简单情况下对我来说似乎不太有用。
阿维(Avi)

3

我使用自己的方法在多重继承场景中安全地调用super(我把所有代码都放了进去)

def safe_super(_class, _inst):
    """safe super call"""
    try:
        return getattr(super(_class, _inst), _inst.__fname__)
    except:
        return (lambda *x,**kx: None)


def with_name(function):
    def wrap(self, *args, **kwargs):
        self.__fname__ = function.__name__
        return function(self, *args, **kwargs)
return wrap

样本用法:

class A(object):

    def __init__():
        super(A, self).__init__()

    @with_name
    def test(self):
        print 'called from A\n'
        safe_super(A, self)()

class B(object):

    def __init__():
        super(B, self).__init__()

    @with_name
    def test(self):
        print 'called from B\n'
        safe_super(B, self)()

class C(A, B):

    def __init__():
        super(C, self).__init__()

    @with_name
    def test(self):
        print 'called from C\n'
        safe_super(C, self)()

测试它:

a = C()
a.test()

输出:

called from C
called from A
called from B

在每个@with_name装饰方法中,您可以访问self .__ fname__作为当前函数名称。


3

我最近尝试使用以上答案从该函数的上下文访问该函数的文档字符串,但由于上述问题仅返回了名称字符串,因此它不起作用。

幸运的是,我找到了一个简单的解决方案。如果像我一样,您要引用该函数,而不是简单地获取表示名称的字符串,您可以将eval()应用于函数名称的字符串。

import sys
def foo():
    """foo docstring"""
    print(eval(sys._getframe().f_code.co_name).__doc__)

3

我建议不要依赖堆栈元素。如果有人在不同的上下文(例如python解释器)中使用您的代码,则您的堆栈将更改并破坏索引([0] [3])。

我建议你这样:

class MyClass:

    def __init__(self):
        self.function_name = None

    def _Handler(self, **kwargs):
        print('Calling function {} with parameters {}'.format(self.function_name, kwargs))
        self.function_name = None

    def __getattr__(self, attr):
        self.function_name = attr
        return self._Handler


mc = MyClass()
mc.test(FirstParam='my', SecondParam='test')
mc.foobar(OtherParam='foobar')

0

用装饰器很容易做到这一点。

>>> from functools import wraps

>>> def named(func):
...     @wraps(func)
...     def _(*args, **kwargs):
...         return func(func.__name__, *args, **kwargs)
...     return _
... 

>>> @named
... def my_func(name, something_else):
...     return name, something_else
... 

>>> my_func('hello, world')
('my_func', 'hello, world')
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.