【问题标题】:Determine function name from within that function (without using traceback)从该函数中确定函数名称(不使用回溯)
【发布时间】:2011-07-01 08:33:08
【问题描述】:

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

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

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

【问题讨论】:

  • 我不明白为什么接受的答案不是来自 Andreas Young(或任何其他显示如何做到这一点)的答案。相反,公认的答案是“你不能那样做”,这似乎是错误的; OP 的唯一要求是不使用traceback。甚至答案和 cmets 的时代似乎也不支持它。
  • 嘿@Rob,您能否详细说明为什么您选择接受的答案作为接受?似乎它目前在这种情况下不相关,因为其他答案确实有效(对我来说),而接受的答案说这是不可能的
  • 对于 Python 3.x 最简单的未被接受的答案 + 请参阅下面的 Vagiz Duseev 的答案Answer

标签: python function introspection traceback


【解决方案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>

【讨论】:

  • 您也可以使用:print(inspect.currentframe().f_code.co_name) 或获取来电者姓名:print(inspect.currentframe().f_back.f_code.co_name)。我认为它应该更快,因为您不像 inspect.stack() 那样检索所有堆栈帧的列表。
  • inspect.currentframe().f_back.f_code.co_name 不适用于装饰方法,而 inspect.stack()[0][3] 则...
  • 请注意:inspect.stack() 会导致大量性能开销,因此请谨慎使用!在我的手臂盒上,它需要 240 毫秒才能完成(出于某种原因)
  • 在我看来,Python 递归机制中的某些东西可能会被用来更有效地做到这一点
  • @Michael 请发表您的评论作为答案。
【解决方案2】:

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

给定的拒绝通知是:

此 PEP 被拒绝。目前尚不清楚它应该如何实现或在边缘情况下应该有什么精确的语义,并且没有给出足够重要的用例。反应充其量只是不冷不热。

【讨论】:

  • inspect.currentframe() 就是这样一种方式。
  • 将@CamHart 的方法与@Yuval 的方法相结合避免了@RoshOxymoron 的答案中的“隐藏”和可能被弃用的方法,以及@neuro/@AndreasJung 的答案的堆栈中的数字索引:print(inspect.currentframe().f_code.co_name)
  • 能否总结一下被拒的原因?
  • 为什么选择这个答案?问题不在于访问当前函数或模块本身,而在于名称。并且堆栈跟踪/调试功能已经有了这些信息。
  • 截至今天,在我的 CPython 3.7.2 bar.__name__ 中测试确实有效。对于 Python 3.x + 最简单的未被接受的答案,请参阅Vagiz Duseev 下方Answer 的答案。
【解决方案3】:

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

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

【讨论】:

  • 如果使用inspect.stack()[0][3],则使用inspect.stack()[0].function,即使堆栈跟踪中的语义发生变化,它也应该更加健壮。
【解决方案4】:

您可以使用the approach that @Andreas Jung shows 获取定义它的名称,但这可能不是调用函数时使用的名称:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

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

【讨论】:

  • .func_name 的情况相同。值得记住的是,Python 中的类名和函数名是一回事,引用它们的变量是另一回事。
  • 有时您可能希望Foo2() 打印Foo。例如:Foo2 = function_dict['Foo']; Foo2()。在这种情况下,Foo2 可能是命令行解析器的函数指针。
  • 这对速度有什么影响?
  • 速度意味着什么?是否存在您需要在硬实时情况下获取此信息的情况或其他情况?
【解决方案5】:
functionNameAsString = sys._getframe().f_code.co_name

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

【讨论】:

  • 完全正常,只使用sys,不需要加载更多模块,但不太容易记住:V
  • @erm3nda 查看我的回答。
  • @nerdfever.com 我的问题不是要创建一个函数,而是不记得在该函数中放什么。不容易记住,所以我总是需要看到一些笔记才能再次构建它。我会尽量记住f 用于框架,co 用于代码。我用的不多,所以如果我把它保存在一些 sn-p 中会更好:-)
  • @m3nda 我的答案是“已删除”,只有我可以看到(我不知道为什么!)就是这样: import sys def thisFunctionName(): """返回一个名为调用它的函数""" return sys._getframe(1).f_code.co_name
  • @Greenonline 我的回答是对 Ron Davis 的回答的详细说明,解决了 m3nda 的问题,它被“版主删除”并且只有我可以看到(我不知道为什么!也许是“抄袭”,但是我相信 Ron Davis 的回答!)就是这样: import sys def thisFunctionName(): """返回一个字符串,其中包含调用它的函数的名称""" return sys._getframe(1).f_code.co_name
【解决方案6】:

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

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

【讨论】:

  • inspect.currentframe() 似乎是执行时间和使用私有成员之间的一个很好的权衡
  • @mbdevpl 我的数字是 1.25ms, 1.24ms, 0.5us, 0.16us normal (nonpythonic :) ) 秒(win7x64, python3.5.1)
  • 没有人认为@mbdevpl 疯了 :) - 我提交了第三次运行输出的编辑,因为它没有任何意义。不知道结果是否应该是 0.100 usec0.199 usec,但无论哪种方式 - 比选项 1 和 2 快数百倍,与选项 4 相当(尽管 Antony Hatchkins 发现选项 3 比选项 4 快三倍)。
  • 我使用sys._getframe().f_code.co_name 而不是inspect.currentframe().f_code.co_name 只是因为我已经导入了sys 模块。这是一个合理的决定吗? (考虑到速度看起来非常相似)
  • 这是一个完整的答案,在我看来应该是公认的答案
【解决方案7】:

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

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

存根

【讨论】:

  • 作为一个装饰新手,我想知道是否有办法在 my_funky_name 的上下文中访问 func.__name__ (这样我就可以检索它的值并在 my_funky_name 中使用它)
  • 在 my_funky_name 函数中执行此操作的方法是 my_funky_name.__name__。您可以将 func.__name__ 作为新参数传递给函数。 func(*args, **kwargs, my_name=func.__name__)。要从函数内部获取装饰器名称,我认为这需要使用检查。但是在我正在运行的函数中获取控制我的函数的函数的名称......好吧,这听起来像是一个美丽的模因的开始:)
【解决方案8】:

我把这个方便的工具放在附近:

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

用法:

myself()

【讨论】:

  • 如何使用此处提出的替代方案来完成? “myself = lambda: sys._getframe().f_code.co_name”不起作用(输出是“”;我认为是因为结果是在定义时确定的,而不是在调用时确定的。嗯。跨度>
  • @prismalytics.io:如果你称自己为 (myself()) 并且不只使用它的值 (myself),你会得到你想要的。
  • NYCeyes 是对的,名称在 lambda 内部解析,因此结果是 。 sys._getframe() 和 inspect.currentframe() 方法必须直接在您想要获取名称的函数内部执行。 inspect.stack() 方法有效,因为您可以指定索引 1,执行 inspect.stack()[0][3] 也会产生 .
【解决方案9】:

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

将@CamHart 和@Yuval 的建议与@RoshOxymoron 的accepted answer 结合起来有利于避免:

  • _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 上测试

【讨论】:

    【解决方案10】:

    你可以使用装饰器:

    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:我的回答 as is 是一个示例,说明了对 OP 问题的回答
    • 好的,my_function是OP的随机用户函数。将此归咎于我对装饰器的理解不足。 @在哪里?这对于您不想调整其参数的函数将如何工作?我如何理解您的解决方案:当我想知道函数名称时,我必须将其附加@get_function_name,并添加名称参数,希望它不存在用于其他目的。我可能遗漏了什么,抱歉。
    • 没有在评论中开始我自己的python课程:1.函数是对象; 2. 你可以给函数附加一个名字属性,打印/记录名字,或者用装饰器中的“名字”做任何事情; 3. 可以通过多种方式附加装饰器(例如 @ 或在我的示例中); 4. 装饰器可以使用@wraps 和/或本身就是类; 5. 我可以继续,但是,快乐的编程!
    • 这看起来像是一种复杂的方式来获取函数的__name__ 属性。使用需要知道你想要得到的东西,在没有动态定义函数的简单情况下,这对我来说似乎不是很有用。
    【解决方案11】:

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

    这是我的看法:

    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() 的时间安排]。

    【讨论】:

    • 在 python 3.7 中工作
    【解决方案12】:
    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

    你好,我是酒吧,爸爸是

    【讨论】:

      【解决方案13】:

      我用自己的方法在多重继承场景中安全地调用 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__ 作为当前函数名。

      【讨论】:

        【解决方案14】:

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

        【讨论】:

          【解决方案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】:

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

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

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

            【讨论】:

              【解决方案17】:

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

              【讨论】:

                【解决方案18】:

                我不知道为什么人们把它弄复杂了:

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

                【讨论】:

                【解决方案19】:

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

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

                【讨论】:

                  【解决方案20】:

                  使用__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 的文档中(参见 Callable types 部分)。

                  每个函数都有一个返回其名称的__name__ 参数,甚至返回其全名的__qualname__ 参数,包括它所属的类(请参阅Qualified name)。

                  【讨论】:

                  • 如果必须已经知道函数名“bar”才能执行此操作,这有什么意义?
                  • @PyNoob:将bar 重命名为foo 后,print('bar') 会愉快地打印(错误地)“bar”,而print(bar.__name__) 则失败。
                  【解决方案21】:
                  str(str(inspect.currentframe())).split(' ')[-1][:-1]
                  

                  【讨论】:

                    【解决方案22】:

                    由于sys._getframe().f_back.f_code.co_namepython 3.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(...)
                    

                    【讨论】:

                    • 除了问题不是关于行号的问题。
                    • @MEMark 我一致地更新了我的答案,你应该已经注意到文本错误,但我也调查了这个问题,我得出的结论是,尽管 IDE PyCharm 调用 sys._getframe().f_back.f_code.co_name 仍然有效无法识别它 在 'sys.pyi | 中找不到参考 '_getframe' sys.pyi'。这就是我之前写这个答案的原因。
                    • @MEMark 这是帖子,我在这个问题上写道:https://stackoverflow.com/q/68772415/5667103
                    【解决方案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
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2018-08-19
                      • 2014-12-14
                      相关资源
                      最近更新 更多