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

在Python中,如果不使用traceback模块,有没有办法从该函数中确定函数的名称?

假设我有一个带有函数bar的模块foo。当执行foo.bar()时,有没有办法让bar知道bar的名字?或者更好的是,foo.bar的名字?

#foo.py
def bar():
print "my name is", __myname__ # <== how do I calculate this at runtime?
398919 次浏览
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>

Python在函数本身中没有访问函数或其名称的功能。它已被但被拒绝。如果你不想自己使用堆栈,你应该根据上下文使用"bar"bar.__name__

给出的拒绝通知是:

这个PEP被拒绝了。目前还不清楚它应该如何实现,也不清楚边缘情况下的精确语义学应该是什么,并且没有给出足够多的重要用例。

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

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

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

import inspect


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


Foo2 = Foo


>>> Foo()
Foo


>>> Foo2()
Foo

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

functionNameAsString = sys._getframe().f_code.co_name

我想要一个非常类似的东西,因为我想把函数名放在一个日志字符串中,这个字符串在我的代码中有很多地方。可能不是最好的方法,但这是一种获取当前函数名称的方法。

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

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

更新08/2021(原帖子是为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

我找到了一个可以写函数名的包装器

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

STUB

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

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

用法:

myself()

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

将@CamHart和@Yuval的建议与@RoshOxymoron的接受的答案相结合,可以避免:

  • _hidden和可能已弃用的方法
  • 索引到堆栈中(可以在未来的蟒蛇中重新排序)

所以我认为这对未来的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))

更新:在3.7.10、3.8.10和3.9.5上测试

您可以使用装饰器:

def my_function(name=None):
return name


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


>>> get_function_name(my_function)
'my_function'

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

以下是我的看法:

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()的帖子和时间]。

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中,代码输出

你好,我是富,爸爸是

你好,我是bar,爸爸是foo

你好,我是酒吧,爸爸是

我做了我自己的方法,用于在多重继承场景中安全调用超级(我把所有代码都放进去了)

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__作为当前函数名称。

print(inspect.stack()[0].function)似乎也可以工作(Python 3.5)。

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

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

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

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

我建议不要依赖堆栈元素。如果有人在不同的上下文中使用您的代码(例如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')

我不知道为什么人们把它变得复杂:

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

这很容易用装饰器完成。

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

使用__name__属性:

# foo.py
def bar():
print(f"my name is {bar.__name__}")

您可以使用__name__属性从函数中轻松访问函数的名称。

>>> def bar():
...     print(f"my name is {bar.__name__}")
...
>>> bar()
my name is bar

我自己多次遇到这个问题,寻找解决方法。正确的答案包含在Python的留档中(参见可调用类型部分)。

每个函数都有一个__name__参数返回其名称,甚至__qualname__参数返回其全名,包括它属于哪个类(参见限定名称)。

str(str(inspect.currentframe())).split(' ')[-1][:-1]

由于sys._getframe().f_back.f_code.co_namepython3.9中根本不起作用,从现在开始可以使用以下内容:

from inspect import currentframe




def testNameFunction() -> str:
return currentframe().f_back.f_code.co_name




print(f'function name is {testNameFunction()}(...)')

结果:

function name is testNameFunction(...)

我喜欢使用装饰器的想法,但我更愿意避免接触函数参数。因此,我提供了另一种选择:

import functools


def withname(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
global __name
__saved_name = globals().get("__name")
__name = f.__name__
ret = f(*args, **kwargs)
__name = __saved_name
return ret
return wrapper


@withname
def f():
print(f"in f: __name=={__name}")
g()
print(f"back in f: __name=={__name}")


@withname
def g():
print(f"in g: __name=={__name}")

我们需要在调用函数时保存和恢复__name,因为它是一个全局变量。调用上面的f()会产生:

in f: __name==f
in g: __name==g
back in f: __name==f

不幸的是,如果我们不更改函数参数,就没有替代global变量的方法。引用不在函数上下文中创建的变量将生成查找全局变量的代码:

>>> def f(): print(__function__)
>>> from dis import dis
>>> dis(f)
1           0 LOAD_GLOBAL              0 (print)
2 LOAD_GLOBAL              1 (__function__)
4 CALL_FUNCTION            1
6 POP_TOP
8 LOAD_CONST               0 (None)
10 RETURN_VALUE
import inspect




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




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




def asdf():
print(method_name_caller())
print(method_name())




def asdf2():
print(method_name_caller())
print(method_name())
asdf()

@Jeff Lowlin的答案很漂亮。我稍微修改了它以实现我认为的意图:跟踪函数的执行,并捕获参数列表以及关键字参数。谢谢@jeff-Lowlin!

from functools import wraps
import time
                                                                                                                                                                                                                                

def named(func):
@wraps(func)
def _(*args, **kwargs):
print(f"From wrapper function: Executing function named: {func.__name__}, with arguments: {args}, and keyword arguments: {kwargs}.")
print(f"From wrapper function: {func}")
start_time = time.time()
return_value = func(*args, **kwargs)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"From wrapper function: Execution of {func.__name__} took {elapsed_time} seconds.")
return return_value
return _
                                                                                                                                                                                                                                

@named
def thanks(message, concepts, username='@jeff-laughlin'):
print(f"From inner function: {message} {username} for teaching me about the {concepts} concepts of closures and decorators!")
                                                                                                                                                                                                                                

thanks('Thank you', 'two', username='@jeff-laughlin')
print('-'*80)
thanks('Thank you', 'two', username='stackoverflow')
print(thanks)

从包装函数:执行名为:谢谢的函数,带有参数:('谢谢','两个')和关键字参数:{'用户名':'@Jeff-劳克林'}。

从包装函数:<函数感谢0x7f13e6ceaa60>

从内部函数:谢谢@jeff-Laulin教我关于闭包和装饰器的两个概念!

从包装函数:执行感谢花了2.193450927734375e-05秒。

--------------------------------------------------------------------------------

从包装函数:执行名为:谢谢的函数,参数:('谢谢','两个')和关键字参数:{'username':'stackoverflow'}。

从包装函数:<函数感谢0x7f13e6ceaa60>

从内部函数:感谢stackoverflow教我关于闭包和装饰器的两个概念!

从包装函数:执行感谢花了7.152557373046875e-06秒。
<0x7f13e6ceaca0上的函数感谢>

最让我惊讶的是,有一种方法可以在运行时拦截函数,检查它们,并基于此采取一些操作。另一个令人惊讶的是,两次内部函数的内存地址都是相同的。有人知道这是为什么吗?在我理解这个装饰器/闭包魔法之前,我还有很长的路要走。

从上面所有使用inspect库的答案来看,似乎都在写这样的东西:

import inspect


inspect.stack()[0][3]

但是,由于inspect.stack()[0]的返回是以下形式的NamedTuple:

FrameInfo(frame=<frame at 0x103578810, file '<stdin>', line 1, code <module>>, filename='<stdin>', lineno=1, function='<module>', code_context=None, index=None)

人们可以简单地用名字来称呼,即inspect.stack()[0].function

这里可以看到一个小的虚拟示例:

    def test_train_UGRIZY_noZ(self, architecture, dataset, hyperrun, wloss):
log.warning(f"{inspect.stack()[0].function} -- Not Implemented Yet")
pass

当运行打印时:

WARNING - test_train_UGRIZY_noZ -- Not Implemented Yet