【问题标题】:Python introspection: access function name and docstring inside function definitionPython自省:在函数定义中访问函数名和文档字符串
【发布时间】:2012-04-06 00:31:22
【问题描述】:

考虑以下python代码:

def function():
    "Docstring"

    name = ???
    doc = ???

    return name, doc

>>> function()
"function", "Docstring"

我需要用什么替换问号,以便从同一个函数中获取函数的名称和文档字符串?

编辑: 到目前为止,大多数答案都明确地将函数的名称硬编码在其定义中。是否可以像下面这样,新函数 get_name_doc 会从调用它的外部框架访问该函数,并返回其名称和文档?

def get_name_doc():
    ???

def function():
    "Docstring"

    name, doc = get_name_doc()

    return name, doc

>>> function()
"function", "Docstring"

【问题讨论】:

    标签: python introspection


    【解决方案1】:

    这是不可能以一致的方式干净地完成的,因为名称可以更改和重新分配。

    但是,只要函数没有被重命名或修饰,你就可以使用它。

    >>> def test():
    ...     """test"""
    ...     doc = test.__doc__
    ...     name = test.__name__
    ...     return doc, name
    ... 
    >>> test()
    ('test', 'test')
    >>> 
    

    这根本不可靠。这是一个出错的例子。

    >>> def dec(f):
    ...     def wrap():
    ...         """wrap"""
    ...         return f()
    ...     return wrap
    ... 
    >>> @dec
    ... def test():
    ...     """test"""
    ...     return test.__name__, test.__doc__
    ... 
    >>> test()
    ('wrap', 'wrap')
    >>> 
    

    这是因为名称 test 在实际创建函数时并未定义,并且是函数中的全局引用。因此,每次执行时都会在全局范围内查找它。因此,在全局范围内更改名称(例如装饰器)会破坏您的代码。

    【讨论】:

    • 在这种情况下,有没有办法获取correct doc 字符串?任何解决问题的方法?
    • @George,请参阅我帖子底部的想法。我曾经玩过类似的东西,只是随便玩弄,但从来没有能够让它在我认为可以生产的水平上工作。
    • 关于这个链接上的inspect.stack()stackoverflow.com/questions/900392/…,他怎么知道访问堆栈的索引是什么?我认为这不是猜测,对吧?如果您可以动态确定要访问哪个stack[x][y],我很感兴趣?
    • @George,第一帧是调用它的帧。它的索引为 0。第二帧是调用调用者的帧。那是他所追求的索引 1。在这里,您将使用索引 0。然后使用第二个索引来查看您获得的内容或阅读源代码。
    • 不管怎样,如果你在装饰器中使用@wraps (docs.python.org/3/library/functools.html#functools.wraps),你可以保留文档字符串。
    【解决方案2】:

    下面的代码解决了函数名的问题。但是,它无法检测到 aaronasterling 给出的示例的正确文档字符串。我想知道是否有办法回到与字节码对象关联的抽象语法树。那么阅读文档字符串就很容易了。

    import inspect
    
    def get_name_doc():
        outerframe = inspect.currentframe().f_back
        name = outerframe.f_code.co_name
        doc = outerframe.f_back.f_globals[name].__doc__    
        return name, doc
    
    if __name__ == "__main__":
    
        def function():
            "Docstring"
    
            name, doc = get_name_doc()
    
            return name, doc
    
        def dec(f):
            def wrap():
               """wrap"""
               return f()
            return wrap
    
        @dec
        def test():
            """test"""
            return get_name_doc()
    
        assert function() == ('function', "Docstring")
        #The assertion below fails:. It gives: ('test', 'wrap')
        #assert test() == ('test', 'test')
    

    【讨论】:

      【解决方案3】:

      这个怎么样:

      import functools
      
      def giveme(func):
          @functools.wraps(func)
          def decor(*args, **kwargs):
              return func(decor, *args, **kwargs)
          return decor
      
      @giveme
      def myfunc(me):
          "docstr"
          return (me.__name__, me.__doc__)
      
      # prints ('myfunc', 'docstr')
      print myfunc()
      

      很快,giveme 装饰器将(装饰的)函数对象添加为第一个参数。这样,函数在被调用时可以访问自己的名称和文档字符串。

      由于装饰,将原来的myfunc函数替换为decor。要使第一个参数与myfunc 完全相同,传入函数的是decor 而不是func

      functools.wraps 装饰器用于为decor 赋予原始myfunc 函数的属性(名称、文档字符串等)。

      【讨论】:

      • 您将包装函数作为第一个参数传递给装饰器,而不是函数...
      • 在您的描述中,您声称传递了函数对象。
      • 我稍微改变了措辞。
      【解决方案4】:

      这将找到调用 get_doc 的函数的名称和文档。 在我的意义上,get_doc 应该将函数作为参数(这会使它变得更容易,但实现起来却不那么有趣;))

      import inspect
      
      def get_doc():
          """ other doc
          """
          frame = inspect.currentframe()
      
          caller_frame = inspect.getouterframes(frame)[1][0]
          caller_name = inspect.getframeinfo(caller_frame).function
          caller_func = eval(caller_name)
      
          return caller_name, caller_func.__doc__
      
      
      def func():
          """ doc string """
          print get_doc()
          pass
      
      
      def foo():
          """ doc string v2 """
          func()
      
      def bar():
          """ new caller """
          print get_doc()
      
      func()
      foo()
      bar()
      

      【讨论】:

      • 需要测试函数调用是否被装饰,但应该不是问题,因为装饰器应该是下一帧,而不是前一帧
      • 如果你不del帧,它会创建一个循环。
      • @also,它因装饰器而失败,因为您只是使用 eval 来评估全局范围内的名称。因此,这与其他人给出的答案相同,除了您使用 inspect 在全局范围内获取 eval 的名称并假设该函数甚至存在而不是,例如,在模块中。
      • 如果在 get_doc 范围内看不到 caller_name 怎么办?
      • @aaronasterling 是的。更是如此,因为有这种函数。令人沮丧的是,农场没有函数的地址而不是函数的名称。
      【解决方案5】:
      >>> import inspect
      >>> def f():
      ...     """doc"""
      ...     name = inspect.getframeinfo(inspect.currentframe()).function
      ...     doc = eval(name + '.__doc__')
      ...     return name, doc
      ... 
      >>> f()
      ('f', 'doc')
      >>> class C:
      ...     def f(self):
      ...         """doc"""
      ...         name = inspect.getframeinfo(inspect.currentframe()).function
      ...         doc = eval(name + '.__doc__')
      ...         return name, doc
      ... 
      >>> C().f()
      ('f', 'doc')
      

      【讨论】:

        【解决方案6】:

        对于我的个人项目,我为函数和类方法开发了函数名称和doc恢复技术。这些是在一个可导入的模块 (SelfDoc.py) 中实现的,该模块在其 ma​​in 中有自己的自检。它包含在下面。此代码在 Linux 和 MacOS 上的 Python 2.7.8 中执行。它正在积极使用中。

        #!/usr/bin/env python
        
        from inspect import (getframeinfo, currentframe, getouterframes)
        
        class classSelfDoc(object):
        
            @property
            def frameName(self):
                frame = getframeinfo(currentframe().f_back)
                return str(frame.function)
        
            @property
            def frameDoc(self):
                frame = getframeinfo(currentframe().f_back)
                doc = eval('self.'+str(frame.function)+'.__doc__')
                return doc if doc else 'undocumented'
        
        def frameName():
            return str(getframeinfo(currentframe().f_back).function)
        
        def frameDoc():
            doc = eval(getframeinfo(currentframe().f_back).function).__doc__
            return doc if doc else 'undocumented'
        
        if __name__ == "__main__":
        
            class aClass(classSelfDoc):
                "class documentation"
        
                def __init__(self):
                    "ctor documentation"
                    print self.frameName, self.frameDoc
        
                def __call__(self):
                    "ftor documentation"
                    print self.frameName, self.frameDoc
        
                def undocumented(self):
                    print self.frameName, self.frameDoc
        
            def aDocumentedFunction():
                "function documentation"
                print frameName(), frameDoc()
        
            def anUndocumentedFunction():
                print frameName(), frameDoc()
        
            anInstance = aClass()
            anInstance()
            anInstance.undocumented()
        
            aDocumentedFunction()
            anUndocumentedFunction()
        

        【讨论】:

          【解决方案7】:

          你必须使用函数的名称来获取它:

          def function():
              "Docstring"
          
              name = function.__name__
              doc = function.__doc__
          
              return name, doc
          

          还有一个叫做inspect的模块: http://docs.python.org/library/inspect.html。 这对于获取有关函数(或任何 python 对象)的更多信息很有用。

          【讨论】:

          • 不显式使用名称是否可以做到?
          • Python 是显式的。
          • 这个答案是错误的和不可靠的。只需尝试应用装饰器。
          【解决方案8】:
          >>> def function():
                  "Docstring"
          
                  name = function.__name__
                  doc = function.__doc__
          
                  return name, doc
          
          >>> function()
          ('function', 'Docstring')
          

          【讨论】:

            【解决方案9】:
            def function():
                "Docstring"
            
                name = function.__name__
                doc = function.__doc__
            
                return name, doc    
            

            应该这样做,使用函数的名称,在你的情况下,function

            这里有一个很好的教程来讨论它:http://epydoc.sourceforge.net/docstrings.html

            当然还有:http://docs.python.org/tutorial/controlflow.html#documentation-strings

            编辑:请参阅您对问题的编辑版本,我认为您可能不得不与inspect.stack() from this SO question 混淆。 Ayman Hourieh 的回答举了一个小例子。

            【讨论】:

              【解决方案10】:

              对于一个硬编码的版本,它可以很好地与“表现良好”的装饰器一起工作。 它必须在函数之后声明。如果函数稍后反弹,则在此处更新更改。

              def get_name_doc():
                  # global function # this is optional but makes your intent a bit more clear.
                  return function.__name__, function.__doc__
              

              这是一个相当讨厌的 hack,因为它滥用了默认 args 的工作方式。它将使用在此函数“初始化”时绑定的任何函数,并且即使该函数被反弹,也要记住它。用 args 调用它会导致有趣的结果。

              def get_name_doc(fn=function):
                  return fn.__name__, fn.__doc__
              

              还有一个动态的,它仍然是硬编码的,但在函数被调用时会更新,参数为 True。基本上这个版本只会在被告知时才会更新。

              def get_name_doc(update=False):
                  global fn
                  if update:
                      fn = function
                  return fn.__name__, fn.__doc__
              

              当然现在也有装饰器的例子。

              @decorator  # applying the decorator decorator to make it well behaved
              def print_name_doc(fn, *args, **kwargs):
                  def inner(*args, **kwargs):
                       print(fn.__doc__, fn.__name__)  # im assuming you just want to print in this case
                       return fn(*args, **kwargs)
              return inner
              

              你应该阅读装饰器装饰器(至少)。 查看 NamedTuple 源代码(来自集合模块),因为它涉及到硬编码。可悲的是,命名元组代码相当奇怪。它是一种与 eval 而非传统代码一起使用的字符串格式,但它工作得非常巧妙。这似乎是最有希望的变体。 您也许也可以使用元类来做到这一点,这会导致代码简洁,但是隐藏在幕后的讨厌的东西,您需要编码。这个id不建议

              我怀疑可能有比进入检查/反射/模板/元类更简单的方法,只需在模块末尾添加以下行。

              help(<module>)
              

              其中是您正在处理的模块的名称(字符串)。甚至是变量 __name__。如果使用多个模块,这也可以在 __init__.py 文件中完成 或个别课程我认为

              【讨论】:

                【解决方案11】:

                正如多次提到的,在函数内部使用函数名实际上是在当前模块的 globals() 中动态查找。使用任何类型的 eval() 只是它的一种变体,因为它的名称解析将再次与 globals() 字典一起使用。大多数示例都会因成员函数而失败 - 您需要先从 globals() 查找类名,然后才能从中访问成员函数。所以其实

                def function():
                    """ foo """
                     doc = function.__doc__
                
                class Class:
                    def function():
                        """ bar """
                        doc = Class.function.__doc__
                

                等价于

                def function():
                    """ foo """
                     doc = globals()["function"].__doc__
                
                class Class:
                    def function():
                        """ bar """
                        doc = globals()["Class"].function.__doc__
                

                在许多情况下,这种动态查找就足够了。但实际上你必须在函数中重新输入函数名。但是,如果您编写一个辅助函数来找出调用者的文档字符串,那么您将面临这样一个事实,即辅助函数可能存在于具有不同 globals() 字典的不同模块中。所以唯一正确的方法是使用当前帧信息来查找函数——但是 Python 的帧对象没有对函数对象的引用,它只携带对它使用的“f_code”代码的引用。它需要通过引用的“f_globals”字典查找从f_code到函数对象的映射,例如这样:

                import inspect
                
                def get_caller_doc():
                    frame = inspect.currentframe().f_back.f_back
                    for objref in frame.f_globals.values():
                        if inspect.isfunction(objref):
                            if objref.func_code == frame.f_code:
                                return objref.__doc__
                        elif inspect.isclass(objref):
                            for name, member in inspect.getmembers(objref):
                                if inspect.ismethod(member):
                                    if member.im_func.func_code == frame.f_code:
                                        return member.__doc__
                

                它被命名为 get_caller_doc() 而不是 get_my_doc() 因为在绝大多数情况下,您确实希望将文档字符串作为参数传递给某个辅助函数。但是辅助函数可以很容易地从它的调用者那里获取文档字符串——我在我的单元测试脚本中使用它,其中一个辅助函数可以使用测试的文档字符串将其发布到一些日志中或将其用作实际的测试数据。这就是为什么提供的帮助程序只查找测试函数和测试成员函数的文档字符串。

                class MyTest:
                    def test_101(self):
                        """ some example test """
                        self.createProject("A")
                    def createProject(self, name):
                        description = get_caller_doc()
                        self.server.createProject(name, description)
                

                留给读者扩展其他用例的示例。

                【讨论】:

                  【解决方案12】:

                  参考http://stefaanlippens.net/python_inspect

                  import inspect
                  # functions
                  def whoami():
                      return inspect.stack()[1][3]
                  def whocalledme():
                      return inspect.stack()[2][3]
                  def foo():
                      print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme())
                      bar()
                  def bar():
                      print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme())
                  johny = bar
                  # call them!
                  foo()
                  bar()
                  johny()
                  

                  输出:

                  hello, I'm foo, daddy is ?
                  hello, I'm bar, daddy is foo
                  hello, I'm bar, daddy is ?
                  hello, I'm bar, daddy is ?
                  

                  【讨论】:

                    【解决方案13】:

                    你可以试试这个:

                    import inspect
                    
                    
                    def my_function():
                        """
                        Hello World!
                        """
                        print(eval(inspect.currentframe().f_code.co_name).__doc__)
                    
                    
                    my_function()  # Hello World!
                    

                    【讨论】:

                      猜你喜欢
                      • 2011-05-17
                      • 2020-07-16
                      • 2020-09-27
                      • 1970-01-01
                      • 2017-02-03
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多