在Python中,如果不使用traceback模块,是否有一种方法可以从函数内部确定函数的名称?
假设我有一个模块foo和一个功能栏。当执行foo.bar()时,是否有一种方法让bar知道bar的名字?或者更好的是,foo。酒吧的名字吗?
#foo.py
def bar():
print "my name is", __myname__ # <== how do I calculate this at runtime?
在Python中,如果不使用traceback模块,是否有一种方法可以从函数内部确定函数的名称?
假设我有一个模块foo和一个功能栏。当执行foo.bar()时,是否有一种方法让bar知道bar的名字?或者更好的是,foo。酒吧的名字吗?
#foo.py
def bar():
print "my name is", __myname__ # <== how do I calculate this at runtime?
当前回答
有几种方法可以达到相同的效果:
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。堆栈调用比替代方法慢数千倍:
$ 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
其他回答
我喜欢使用装饰器的想法,但我更喜欢避免触及函数参数。因此,我提供了另一种选择:
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是一个全局变量,所以在调用函数时需要保存和恢复__name。调用上面的f()会产生:
in f: __name==f
in g: __name==g
back in f: __name==f
不幸的是,如果我们不改变函数参数,就没有全局变量的替代品。引用一个不是在函数上下文中创建的变量,将生成寻找全局变量的代码:
>>> 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 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。堆栈调用比替代方法慢数千倍:
$ 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
我想检查是最好的方法。例如:
import inspect
def bar():
print("My name is", inspect.stack()[0][3])
你可以使用装饰器:
def my_function(name=None):
return name
def get_function_name(function):
return function(name=function.__name__)
>>> get_function_name(my_function)
'my_function'
你可以使用@Andreas Jung显示的方法获得它定义时的名称,但这可能不是调用函数时的名称:
import inspect
def Foo():
print inspect.stack()[0][3]
Foo2 = Foo
>>> Foo()
Foo
>>> Foo2()
Foo
这种区别对你来说是否重要,我不能说。