从该函数内部确定函数名称
- 2025-01-13 08:53:00
- admin 原创
- 102
问题描述:
有没有办法从函数内部确定函数的名称?
def foo():
print("my name is", __myname__) # <== how do I calculate this at runtime?
在上面的例子中, 的主体foo
将以某种方式访问函数名称,"foo"
而无需对其进行硬编码。输出将是:
>>> foo()
my name is foo
解决方案 1:
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>
解决方案 2:
如果您不想自己操作堆栈,那么您应该根据上下文使用"bar"
或。bar.__name__
Python 没有在函数本身内访问函数或其名称的功能。有人__function__
曾为 Python 3.0 提出过一个魔法,但被拒绝了。请参阅PEP 3130 – 访问当前模块/类/函数。
给出的拒绝通知是:
此 PEP 被拒绝。它不清楚应该如何实现,也不清楚在边缘情况下应该有什么样的精确语义,也没有给出足够多的重要用例。回应最多只是冷淡。
解决方案 3:
有几种方法可以得到相同的结果:
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 年 8 月(原帖为 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
解决方案 4:
functionNameAsString = sys._getframe().f_code.co_name
我想要一个非常类似的东西,因为我想将函数名称放在日志字符串中,该字符串出现在我的代码中的许多地方。这可能不是最好的方法,但这里有一种方法可以获取当前函数的名称。
解决方案 5:
您可以使用@Andreas Jung 展示的方法获取定义该函数的名称,但这可能不是调用该函数的名称:
import inspect
def Foo():
print inspect.stack()[0][3]
Foo2 = Foo
>>> Foo()
Foo
>>> Foo2()
Foo
我无法说这种区别对你来说是否重要。
解决方案 6:
这实际上是从该问题的其他答案中得出的。
以下是我的看法:
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() 的时间对比]。
解决方案 7:
我把这个方便的工具放在附近:
import inspect
myself = lambda: inspect.stack()[1][3]
用法:
myself()
解决方案 8:
我想inspect
这是最好的办法。例如:
import inspect
def bar():
print("My name is", inspect.stack()[0][3])
解决方案 9:
我找到了一个可以写入函数名称的包装器
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()
这将打印
我的时髦名字
存根
解决方案 10:
使用__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__
一个返回其全名的参数,包括它所属的类(参见限定名称)。
解决方案 11:
print(inspect.stack()[0].function)
似乎也有效(Python 3.5)。
解决方案 12:
我不知道为什么人们会把它复杂化:
import sys
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))
解决方案 13:
这是一种面向未来的方法。
将@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))
更新:已在 3.7.10、3.8.10 和 3.9.5 上测试
解决方案 14:
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
你好,我是酒吧,爸爸是
解决方案 15:
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
解决方案 16:
使用装饰器可以很容易地实现这一点。
>>> 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')
解决方案 17:
您可以使用装饰器:
def my_function(name=None):
return name
def get_function_name(function):
return function(name=function.__name__)
>>> get_function_name(my_function)
'my_function'
解决方案 18:
从上面所有使用该库的答案来看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
解决方案 19:
我用自己的方法在多重继承场景中安全地调用超级(我把所有的代码都放出来了)
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
'
safe_super(A, self)()
class B(object):
def __init__():
super(B, self).__init__()
@with_name
def test(self):
print 'called from B
'
safe_super(B, self)()
class C(A, B):
def __init__():
super(C, self).__init__()
@with_name
def test(self):
print 'called from C
'
safe_super(C, self)()
测试它:
a = C()
a.test()
输出:
called from C
called from A
called from B
在每个 @with_name 修饰的方法中,您可以访问 self.__fname__ 作为当前函数名称。
解决方案 20:
我建议不要依赖堆栈元素。如果有人在不同的上下文中使用你的代码(例如 Python 解释器),你的堆栈将会改变并破坏你的索引(0)。
我向你提出这样的建议:
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')
解决方案 21:
我最近尝试使用上述答案从该函数的上下文访问该函数的文档字符串,但由于上述问题仅返回名称字符串,因此它不起作用。
幸运的是,我找到了一个简单的解决方案。如果您像我一样想要引用该函数,而不是简单地获取表示名称的字符串,则可以将 eval() 应用于函数名称的字符串。
import sys
def foo():
"""foo docstring"""
print(globals()[sys._getframe().f_code.co_name].__doc__)
解决方案 22:
由于在python 3.9sys._getframe().f_back.f_code.co_name
中根本不起作用,因此从现在开始可以使用以下命令:
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(...)
解决方案 23:
我喜欢使用装饰器的想法,但我更愿意避免接触函数参数。因此,我提供了另一种选择:
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
解决方案 24:
@jeff-laughlin 的回答很棒。我对其进行了些许修改,以实现我认为的意图:追踪函数的执行,并捕获参数列表以及关键字参数。谢谢 @jeff-laughlin!
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)
来自包装函数:执行名为:thanks 的函数,参数为:('Thank you', 'two'),关键字参数为:{'username': '@jeff-laughlin'}。来自包装函数:<function thanks
at0x7f13e6ceaa60>
来自内部函数:感谢 @jeff-laughlin 教我闭包和装饰器的两个概念!
来自包装函数:执行 thanks 耗时 2.193450927734375e-05 秒。--------------------------------------------------------------------------------
来自包装函数:执行名为:thanks 的函数,参数为:('Thank you', 'two'),关键字参数为:{'username': 'stackoverflow'}。来自
包装函数:<function thanks at 0x7f13e6ceaa60>
来自内部函数:感谢 stackoverflow 教我闭包和装饰器的两个概念!
来自包装函数:执行感谢耗时 7.152557373046875e-06 秒。
<function thanks at 0x7f13e6ceaca0>
最让我吃惊的是,有一种方法可以在运行时拦截函数,检查它们,并据此采取一些行动。另一个令人惊讶的事情是内部函数的内存地址两次都相同。有人知道这是为什么吗?我还有很长的路要走,才能理解这个装饰器/闭包魔法。
解决方案 25:
str(str(inspect.currentframe())).split(' ')[-1][:-1]
解决方案 26:
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()
解决方案 27:
实现你自己的装饰器
foo.py
from mydecorators import *
@print_my_name
def bar():
#do something else
pass
#in terminal: my name is: bar
mydecorators.py
def resolve_function(func):
#in case annotated func is an staticmethod
if isinstance(func,staticmethod):
return func.__func__
return func
def print_my_name(func):
def function_caller(*args,**kwargs):
_func = resolve_function(func)
print("my name is: %s" %_func.__name__)
return _func(*args,**kwargs)
return function_caller