如果装饰器更改API,我应该如何处理装饰函数的docstring?

时间:2012-12-18 17:20:47

标签: python api decorator docstring

背景

我有一个包含多个读取或写入文件的函数的库。每个函数都接受该文件作为第一个参数,作为文件对象或文件名。因此,所有函数在开头都具有相同的代码段,类似于以下内容:

if isinstance(f, str):
    file_obj = open(f, 'w')
else:
    file_obj = f

现在我想我可以在装饰器中编写一次并将所有函数包装在其中,而不是重复自己。 (我也在考虑在同一个装饰器中实现上下文管理器接口。)

因此,如果我这样做,函数将如下所示:

@file_aware('w')
def function(f, *args, **kwargs):
    """Do stuff. `f` can be file object or file name"""
    for line in f:
       ....

问题

我担心的是现在函数的docstring与它下面的代码不对应。 (我打算用functools.wraps保留装饰函数的docstring。) 它是否会降低代码的可读性/可维护性/透明度? 根据我的理解,装饰器可以很容易地来去,但同时这一种改变了API(我不打算删除功能)。这种情况的“最佳实践”方式是什么?

我可以考虑在装饰器内自动处理docstring,但是:

  • 这不是最自然的事情;
  • 只对在线文档有意义,并且不会帮助(相反,相反)有人阅读源代码。

1 个答案:

答案 0 :(得分:3)

一种选择是将docstring传递给装饰器。这样你就可以通过函数定义获得一个用于读取源代码的文档字符串,但如果你要更改或删除装饰器,你最终会得到一个不正确的文档字符串。

例如:

@file_aware(docstring="Do stuff. `f` can be file object or file name", mode="r")
def function(f, *args, **kwargs):
    for line in f:
       ....

您的file_aware装饰器可能看起来像这样:

def file_aware(docstring, mode):
    def deco(func):
        @functools.wraps(func)
        def wrapped(f, *args, **kwargs):
            if isinstance(f, str):
                file_obj = open(f, mode)
            else:
                file_obj = f
            return func(file_obj, *args, **kwargs)
        wrapped.__doc__ = docstring
        return wrapped
    return deco