动态函数文档字符串
我想写一个Python函数,这个函数的文档字符串是动态生成的。简单来说,对于一个函数func()
,我希望func.__doc__
能调用一个自定义的__get__
函数来根据需要生成文档字符串。这样,当我使用help(func)
时,就能看到这个动态生成的文档字符串。
这里的背景是,我想写一个Python包,用来封装很多现有分析包中的命令行工具。每个工具都会变成一个同名的模块函数(通过函数工厂创建并插入到模块命名空间中),而这些函数的文档和接口参数则是通过分析包动态生成的。
3 个回答
与其去折腾那个函数,不如自己写一个 help
函数呢?
my_global=42
def help(func):
print('%s: my_global=%s'%(func.func_name,my_global))
def foo():
pass
help(foo)
(Python 3 解决方案)
你可以利用Python的鸭子类型来实现一个动态字符串:
import time
def fn():
pass
class mydoc( str ):
def expandtabs( self, *args, **kwargs ):
return "this is a dynamic strting created on {}".format( time.asctime() ).expandtabs( *args, **kwargs )
fn.__doc__ = mydoc()
help( fn )
注意事项:
这个方法假设 help
函数会调用 .expandtabs
来从 __doc__
对象中获取文本,这在Python 3.7中是有效的。一个更稳妥的解决方案是实现其他的 str
方法,这样即使 help
方法发生变化,我们的“鸭子”依然能像鸭子一样工作。此外,请注意我们的 mydoc
类是从 str
继承的,这是因为 help
函数有点特别,它通过检查 isinstance(thing.__doc__, str)
来强制要求类型匹配。像所有的解决方案一样,这个方法有点小技巧,但是否有问题主要取决于整个项目的需求。
你想做的事情,按照你想要的方式是行不通的。
从你的描述来看,似乎可以尝试这样做:
for tool in find_tools():
def __tool(*arg):
validate_args(tool, args)
return execute_tool(tool, args)
__tool.__name__ = tool.name
__tool.__doc__ = compile_docstring(tool)
setattr(module, tool.name, __tool)
也就是说,在你创建函数的时候,动态生成文档字符串。有没有什么原因让文档字符串在每次调用__doc__
时都必须是动态的呢?
假设确实有这样的原因,你需要把你的函数放到一个类里面,用__call__
来触发这个动作。
但即便如此,你还是会遇到问题。当调用help()来查找文档字符串时,它是针对类而不是实例进行的,所以像这样的代码:
class ToolWrapper(object):
def __init__(self, tool):
self.tool = tool
self.__name__ = tool.name
def _get_doc(self):
return compile_docstring(self.tool)
__doc__ = property(_get_doc)
def __call__(self, *args):
validate_args(args)
return execute_tool(tool, args)
是行不通的,因为属性是实例属性,而不是类属性。你可以通过把文档属性放在元类上,而不是直接放在类上来让它工作。
for tool in find_tools():
# Build a custom meta-class to provide __doc__.
class _ToolMetaclass(type):
def _get_doc(self):
return create_docstring(tool)
__doc__ = property(_get_doc)
# Build a callable class to wrap the tool.
class _ToolWrapper(object):
__metaclass__ = _ToolMetaclass
def _get_doc(self):
return create_docstring(tool)
__doc__ = property(_get_doc)
def __call__(self, *args):
validate_args(tool, args)
execute_tool(tool, args)
# Add the tool to the module.
setattr(module, tool.name, _ToolWrapper())
现在你可以这样做:
help(my_tool_name)
就能得到自定义的文档字符串,或者
my_tool_name.__doc__
也能得到同样的效果。__doc__
属性在_ToolWrapper
类中是必要的,用来处理后者的情况。