动态函数文档字符串

16 投票
3 回答
13806 浏览
提问于 2025-04-15 21:55

我想写一个Python函数,这个函数的文档字符串是动态生成的。简单来说,对于一个函数func(),我希望func.__doc__能调用一个自定义的__get__函数来根据需要生成文档字符串。这样,当我使用help(func)时,就能看到这个动态生成的文档字符串。

这里的背景是,我想写一个Python包,用来封装很多现有分析包中的命令行工具。每个工具都会变成一个同名的模块函数(通过函数工厂创建并插入到模块命名空间中),而这些函数的文档和接口参数则是通过分析包动态生成的。

3 个回答

-4

与其去折腾那个函数,不如自己写一个 help 函数呢?

my_global=42

def help(func):
    print('%s: my_global=%s'%(func.func_name,my_global))        

def foo():
    pass

help(foo)
3

(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) 来强制要求类型匹配。像所有的解决方案一样,这个方法有点小技巧,但是否有问题主要取决于整个项目的需求。

15

你想做的事情,按照你想要的方式是行不通的。

从你的描述来看,似乎可以尝试这样做:

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类中是必要的,用来处理后者的情况。

撰写回答