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') >>> |
这是因为名称
下面的代码解决了函数名称的问题。但是,它无法检测到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() |
不久,
由于装饰,原始
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 |
这应该这样做,使用函数的名称,在你的情况下,
这是一个非常好的教程,讨论它:http://epydoc.sourceforge.net/docstrings.html
当然还有:http://docs.python.org/tutorial/controlflow.html#documentation-strings
编辑:请参阅您编辑的问题版本,我想您可能不得不从这个SO问题中弄乱
你必须使用函数的名称来获取它:
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对象)的更多信息非常有用。