带参数的装饰器

时间:2017-07-31 21:49:48

标签: python python-3.x decorator docstring

我有一组函数(主要是)共享参数但不同的进程。我想使用装饰器将每个参数的描述添加到函数的标题级文档字符串中。

我试图通过在appender中合并嵌套函数来模仿this answer中找到的结构但是失败了。我也试过了functools.partial,但有些事情稍微偏了。

我的尝试:

def appender(func, *args):
    """Appends additional parameter descriptions to func's __doc__."""
    def _doc(func):
        params = ''.join([defaultdocs[arg] for arg in args])
        func.__doc__ += '\n' + params
        return func
    return _doc

defaultdocs = {

    'a' : 
    """
    a : int, default 0
        the first parameter
    """,

    'b' : 
    """
    b : int, default 1
        the second parameter
    """
    }

@appender('a')
def f(a):
    """Title-level docstring."""
    return a 

@appender('a', 'b')
def g(a, b):
    """Title-level docstring."""
    return a + b

这失败了,我失败了,我相信因为传递给appender的第一个arg被解释为func。因此,当我查看g的结果文档字符串时,我得到:

print(g.__doc__)
Title-level docstring.

    b : int, default 1
        the second parameter

因为,当我希望'a'成为'func'的第一个元素时,*args被解释为print(g.__doc__) Title-level docstring. a : int, default 0 the first parameter b : int, default 1 the second parameter 。我怎么能纠正这个?

期望的结果:

let gSmall, gLarge, defaultOrder = 
    if g1remainingNodesToMap.Count <= g2remainingNodesToMap.Count then
        g1remainingNodesToMap, g2remainingNodesToMap, true
    else g2remainingNodesToMap, g1remainingNodesToMap, false

2 个答案:

答案 0 :(得分:4)

这是因为您传递的变量名实际上被捕获到func参数中。

为了在Python中执行可调用装饰器,您需要对函数进行两次编码,使用外部函数来接受装饰器参数,使用内部函数来接受原始函数。可调用装饰器只是返回其他装饰器的高阶函数。例如:

def appender(*args):  # This is called when a decorator is called,
                      # e. g. @appender('a', 'b')
    """Appends additional parameter descriptions to func's __doc__."""
    def _doc(func):  # This is called when the function is about
                     # to be decorated
        params = ''.join([defaultdocs[arg] for arg in args])
        func.__doc__ += '\n' + params
        return func
    return _doc

外部(appender)函数充当新装饰器的工厂,而_doc函数是实际的装饰器。总是以这种方式传递:

  • 将装饰器args传递给外部函数
  • 将原始功能传递给内部功能

一旦Python看到了这个:

@appender('a', 'b')
def foo(): pass

......它会在幕后做这样的事情:

foo = appender('a', 'b')(foo)

......扩展到这个:

decorator = appender('a', 'b')
foo = decorator(foo)

由于Python中的作用域如何工作,每个新返回的_doc函数实例将具有来自外部函数的自己的本地args值。

答案 1 :(得分:1)

使用inspect.signature收集传递的函数参数的替代解决方案。

import inspect
import textwrap

def appender(defaultdocs):
    def _doc(func):
        params = inspect.signature(func).parameters 
        params = [param.name for param in params.values()] 
        params = ''.join([textwrap.dedent(defaultdocs[param]) 
                      for param in params])
        func.__doc__ += '\n\nParameters\n' + 10 * '=' + params
        return func

    return _doc

示例:

# default docstrings for parameters that are re-used often
# class implementation not a good alternative in my specific case

defaultdocs = {

    'a' : 
    """
    a : int, default 0
        the first parameter""",

    'b' : 
    """
    b : int, default 1
        the second parameter"""
    }

@appender
def f(a):
    """Title-level docstring."""
    return a

@appender
def g(a, b):
    """Title-level docstring."""
    return a + b

这会将ab的说明附加到g.__doc__,而无需在装饰器中指定它们:

help(g)
Help on function g in module __main__:

g(a, b)
    Title-level docstring.

    Parameters
    ==========
    a : int, default 0
        the first parameter
    b : int, default 1
        the second parameter