如何将函数名作为字符串获取?

1059

如何将函数名作为字符串获取?

def foo():
    pass

>>> name_of(foo)
"foo"

getattr(func, "__name__", str(func))是我使用的方式,以防它是一个没有__name__属性的Mock(或其他对象)。 - Intrastellar Explorer
14个回答

1357
my_function.__name__

使用__name__是首选方法,因为它可以统一应用。不像func_name,它也适用于内置函数:

>>> import time
>>> time.time.func_name
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'builtin_function_or_method' object has no attribute 'func_name'
>>> time.time.__name__ 
'time'

双下划线也向读者表明这是一个特殊属性。作为额外的奖励,类和模块也有一个__name__属性,因此您只需要记住一个特殊名称。


446
有时候,在函数中作为参数传递的是一个函数对象,你需要显示/存储/操作该函数的名称。可能是因为你正在生成文档、帮助文本、操作历史记录等内容。因此,并非总是硬编码函数名称。 - mbargiel
2
@mgargiel:我的意思是:假设你有一个定义了100个方法的类,其中所有方法都只是包装器,调用一个私有方法,传递包装器本身的名称。就像这样:def wrapper(kwargs): super(ExtendedClass,self).call('wrapper', kwargs)。你还有另一种选择,那就是:def wrapper(kwargs): super(ExtendedClass,self).call(sys._getframe().f_code.co_name, kwargs)。所以,Albert Vonpupp的答案对我来说更好。 - Richard Gomes
19
@RichardGomes 有一个答案适用于在函数本身内获取名称,另一个适用于从函数引用中获取名称。根据OP的问题描述,后者更为合适。 - Russell Borogove
28
@RichardGomes 实际上我来到这个问题是为了找到解决这个问题的方法。我想解决的问题是创建一个装饰器,该装饰器可以用于记录我的所有调用。 - ali-hussain
24
函数是一等对象,它们可以有多个名称绑定到它们。f.__name__ == 'f'并不一定成立。 - wim
显示剩余3条评论

411

若要从函数或方法内部获取当前函数或方法的名称,请考虑:

import inspect

this_function_name = inspect.currentframe().f_code.co_name

sys._getframe也可以代替inspect.currentframe,尽管后者避免了访问私有函数的问题。

如果想要获取调用函数的名称,请考虑使用f_back,例如:inspect.currentframe().f_back.f_code.co_name


如果还使用mypy,它可能会抱怨:

错误: "Optional [FrameType]"的项目"None"没有属性"f_code"

要抑制上述错误,请考虑:

import inspect
import types
from typing import cast

this_function_name = cast(types.FrameType, inspect.currentframe()).f_code.co_name

63
这是我想要看到的答案。其他答案假设呼叫者已经知道函数名,在这个问题的情境下这是荒谬的。 - Richard Gomes
126
Richard:不是这样。你假设在与函数定义相同的作用域中直接调用 name 或 func_name,但这种情况往往并不成立。请记住,函数是对象——它们可以作为参数传递给其他函数,在列表/字典中存储以供稍后查找或执行等。 - mbargiel
13
@paulus_almighty,深入堆栈帧对我来说并不抽象!实际上,这有点相反。请参见文档中的实现细节注释。并非所有Python的实现都包含sys._getframe--它直接与CPython的内部连接。 - senderle
8
只有在函数内部才能起作用,但问题规定不应该调用该函数。 - user2357112
7
如果您想将函数封装成更易使用和记忆的形式,您需要获取第1帧,例如 sys._getframe(1).f_code.co_name,这样您就可以定义一个名为 get_func_name() 的函数,并期望获得调用该函数的所需函数名称。 - m3nda
显示剩余2条评论

52

如果您对类方法也感兴趣,Python 3.3+除了__name__之外,还有__qualname__

def my_function():
    pass

class MyClass(object):
    def method(self):
        pass

print(my_function.__name__)         # gives "my_function"
print(MyClass.method.__name__)      # gives "method"

print(my_function.__qualname__)     # gives "my_function"
print(MyClass.method.__qualname__)  # gives "MyClass.method"

如果方法有@property装饰器,则无法使用此方法,有没有任何方法可以访问此实例中的名称? - dusio
3
看起来你可以从属性的getter中获取名称/全限定名称(使用@property装饰器创建):MyClass.prop.fget.__qualname__ 可以得到 MyClass.prop - lapis

47
my_function.func_name

函数还有其他有趣的属性。输入 dir(func_name) 列出它们。 func_name.func_code.co_code 是已编译的函数,存储为字符串。

import dis
dis.dis(my_function)

将以近乎人类可读的格式显示代码。 :)


5
f.__name__和f.func_name有什么区别? - Federico A. Ramponi
16
Sam: __姓名__是私人的,而__用户名__则很特殊,这两者在概念上有所不同。 - Matthew Trevor
14
如果有人对Matthew之前的回答感到困惑,那么评论系统将某些双下划线解释为加粗字体的代码。使用反引号进行转义,则应该这样写:__names__是私有的,__names是特殊的。 - gwideman
17
实际上,我认为正确的做法是在变量名前加一个下划线 _ 表示其为私有变量(这只是一种约定俗成的做法),而双下划线 __ 包围的变量名表示特殊变量。不确定在变量名前面添加双下划线是否具有任何形式上或惯例上的含义。 - MestreLion
20
在Python3中,func_name已经不存在了,如果想保持兼容性,需要使用func.__name__ - Daenyth
显示剩余4条评论

39

这个函数将返回调用者的函数名称。

def func_name():
    import traceback
    return traceback.extract_stack(None, 2)[0][2]

就像Albert Vonpupp的回答一样,只是多了一个友好的包装。


2
我在索引[2]处有“<module>”,但以下方法有效:traceback.extract_stack(None, 2)[0][-1]。 - emmagras
4
对我来说这个不起作用,但这个可以:traceback.extract_stack()[-1][2] - mike01010
如果将第一个索引更改为1,这将起作用,你们应该在发表评论之前学会调试...traceback.extract_stack(None, 2)[1][2] - Jcc.Sanabria

38
import inspect

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

在哪里

  • stack()[0] 是调用者

  • stack()[3] 是方法的字符串名称


2
简单明了。stack()[0] 总是调用者, [3] 是方法的字符串名称。 - kontur

29

我喜欢使用函数装饰器。我添加了一个类,也会计算函数运行时间。假设 gLog 是一个标准的 Python 日志记录器:

class EnterExitLog():
    def __init__(self, funcName):
        self.funcName = funcName

    def __enter__(self):
        gLog.debug('Started: %s' % self.funcName)
        self.init_time = datetime.datetime.now()
        return self

    def __exit__(self, type, value, tb):
        gLog.debug('Finished: %s in: %s seconds' % (self.funcName, datetime.datetime.now() - self.init_time))

def func_timer_decorator(func):
    def func_wrapper(*args, **kwargs):
        with EnterExitLog(func.__name__):
            return func(*args, **kwargs)

    return func_wrapper

现在你只需要装饰一下你的函数,就大功告成了。

@func_timer_decorator
def my_func():

22

如果您只想获取函数名称,这里有一个简单的代码。 假设您已经定义了这些函数。

def function1():
    print "function1"

def function2():
    print "function2"

def function3():
    print "function3"
print function1.__name__

输出结果将会是function1

现在假设你有一个函数列表

a = [function1 , function2 , funciton3]

获取函数的名称

for i in a:
    print i.__name__

输出结果将为

函数1
函数2
函数3


21
作为@Demyn's answer的延伸,我创建了一些实用函数,可以打印当前函数的名称和当前函数的参数:
import inspect
import logging
import traceback

def get_function_name():
    return traceback.extract_stack(None, 2)[0][2]

def get_function_parameters_and_values():
    frame = inspect.currentframe().f_back
    args, _, _, values = inspect.getargvalues(frame)
    return ([(i, values[i]) for i in args])

def my_func(a, b, c=None):
    logging.info('Running ' + get_function_name() + '(' + str(get_function_parameters_and_values()) +')')
    pass

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter(
    '%(asctime)s [%(levelname)s] -> %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

my_func(1, 3) # 2016-03-25 17:16:06,927 [INFO] -> Running my_func([('a', 1), ('b', 3), ('c', None)])

19

sys._getframe() 并不保证在所有 Python 实现中都能使用(参见参考文献),您可以使用 traceback 模块来完成相同的操作,例如:

import traceback
def who_am_i():
   stack = traceback.extract_stack()
   filename, codeline, funcName, text = stack[-2]

   return funcName

调用stack[-1]将返回当前进程的详细信息。


抱歉,如果sys._getframe()未定义,则traceback.extract_stack也无法使用。后者提供了前者功能的粗略超集;您不能期望只看到其中一个。实际上,在IronPython 2.7中,extract_stack()始终返回[]。-1 - SingleNegationElimination

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