Python内省:在函数定义中访问函数名和docstring

时间:2012-04-06 00:31:23

标签: python introspection

考虑以下python代码:

def function():
    "Docstring"

    name = ???
    doc = ???

    return name, doc

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

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

编辑: 到目前为止,大多数答案都明确地在其定义中硬编码函数的名称。是否有可能执行类似下面的操作,新函数get_name_doc将从调用它的外框访问该函数,并返回其名称和doc?

def get_name_doc():
    ???

def function():
    "Docstring"

    name, doc = get_name_doc()

    return name, doc

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

13 个答案:

答案 0 :(得分:14)

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

但是,只要不重命名或修饰该功能,就可以使用它。

>>> 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,并且该函数是函数中的全局引用。因此,它会在每次执行时都在全局范围内查找。因此,对全局范围中的名称(例如装饰器)的更改将破坏您的代码。

答案 1 :(得分:6)

下面的代码解决了函数名称的问题。但是,它无法检测到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')

答案 2 :(得分:2)

这将找到调用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()

答案 3 :(得分:2)

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

答案 4 :(得分:2)

对于我的个人项目,我为函数和类方法开发了函数名和 doc 恢复技术。这些是在可导入的模块(SelfDoc.py)中实现的,该模块在 main 中有自己的自检。它包括在下面。此代码在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()

答案 5 :(得分:1)

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

def function():
    "Docstring"

    name = function.__name__
    doc = function.__doc__

    return name, doc

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

答案 6 :(得分:1)

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的答案给出了一个小例子。

答案 7 :(得分:1)

>>> def function():
        "Docstring"

        name = function.__name__
        doc = function.__doc__

        return name, doc

>>> function()
('function', 'Docstring')

答案 8 :(得分:1)

这个怎么样:

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装饰器将(装饰的)函数对象添加为第一个参数。这样,函数可以在调用时访问自己的名称和docstring。

由于装饰,原始myfunc功能被decor取代。要使第一个参数与myfunc完全相同,传递给函数的是decor而不是func

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

答案 9 :(得分:1)

对于硬编码版本,与“表现良好”的装饰者合作得体。 它必须在函数后声明。如果函数稍后反弹,则更新会在此处更新。

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

这是一个相当讨厌的黑客,因为它滥用默认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一起使用的字符串格式,而不是传统的代码,但它的工作原理非常整齐。这似乎是最有希望的变种。 您也许可以使用metaclasess执行此操作,这会导致整洁的代码,但隐藏在幕后的令人讨厌的东西,您需要编写代码。这个id建议反对

我怀疑通过简单地在模块末尾添加以下行,可能比进入检查/反射/模板/ metaclasess更简单。

help(<module>)

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

答案 10 :(得分:1)

如前所述,使用函数内部的函数名实际上是当前模块的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的框架对象没有对函数对象的引用,它只带有对&#34; f_code&#34的引用;它使用的代码。它需要搜索引用的&#34; f_globals&#34;用于查找从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(),因为在绝大多数情况下,您确实希望将doc字符串作为某个辅助函数的参数传递给它。但是辅助函数可以很容易地从调用者那里获取doc字符串 - 我在我的unittest脚本中使用它,其中帮助函数可以使用测试的doc字符串将其发布到某些日志中或将其用作实际测试数据。这就是为什么提供的帮助程序只查找测试函数和测试成员函数的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)

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

答案 11 :(得分:1)

参考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 ?

答案 12 :(得分:0)

您可以尝试以下方法:

import inspect


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


my_function()  # Hello World!