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

Python introspection: access function name and docstring inside function definition

考虑以下python代码:

1
2
3
4
5
6
7
8
9
10
def function():
   "Docstring"

    name = ???
    doc = ???

    return name, doc

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
def get_name_doc():
    ???

def function():
   "Docstring"

    name, doc = get_name_doc()

    return name, doc

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

由于名称可以更改和重新分配,因此无法以一致的方式干净地执行此操作。

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

1
2
3
4
5
6
7
8
9
>>> def test():
...    """test"""
...     doc = test.__doc__
...     name = test.__name__
...     return doc, name
...
>>> test()
('test', 'test')
>>>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def dec(f):
...     def wrap():
...        """wrap"""
...         return f()
...     return wrap
...
>>> @dec
... def test():
...    """test"""
...     return test.__name__, test.__doc__
...
>>> test()
('wrap', 'wrap')
>>>

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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')


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> 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')

这将找到调用get_doc的函数的名称和文档。
在我看来,get_doc应该将函数作为参数(这会使它变得更容易,但实现的方式却少得多;))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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()


参考http://stefaanlippens.net/python_inspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()

输出:

1
2
3
4
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 ?


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

1
2
3
4
5
6
7
8
def function():
   """ foo"""
     doc = function.__doc__

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

相当于

1
2
3
4
5
6
7
8
def function():
   """ foo"""
     doc = globals()["function"].__doc__

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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字符串的原因。

1
2
3
4
5
6
7
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)

读者可以扩展其他用例的示例。


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

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

这是一个相当讨厌的黑客,因为它滥用默认args的工作方式。它将使用此函数"初始化"时绑定的任何函数,并记住它,即使函数反弹。用args调用它会带来有趣的结果。

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

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

1
2
3
4
5
def get_name_doc(update=False):
    global fn
    if update:
        fn = function
    return fn.__name__, fn.__doc__

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

1
2
3
4
5
6
@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更容易。

1
help(<module>)

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


这个怎么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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函数的属性(名称,文档字符串等)。


1
2
3
4
5
6
7
8
9
10
>>> def function():
       "Docstring"

        name = function.__name__
        doc = function.__doc__

        return name, doc

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

1
2
3
4
5
6
7
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

编辑:请参阅您编辑的问题版本,我想您可能不得不从这个SO问题中弄乱inspect.stack()。 Ayman Hourieh的答案给出了一个小例子。


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

1
2
3
4
5
6
7
def function():
   "Docstring"

    name = function.__name__
    doc = function.__doc__

    return name, doc

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