【问题标题】:Decorators with parameters带参数的装饰器
【发布时间】:2018-01-07 14:42:21
【问题描述】:

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

我试图通过在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 的第一个参数被解释为func。因此,当我查看 g 的结果文档字符串时,我得到:

print(g.__doc__)
Title-level docstring.

    b : int, default 1
        the second parameter

因为,当我希望*args 成为*args 的第一个元素时,'a' 再次被解释为'func'。我该如何纠正这个问题?

想要的结果:

print(g.__doc__)
Title-level docstring.

    a : int, default 0
        the first parameter

    b : int, default 1
        the second parameter

【问题讨论】:

    标签: python python-3.x decorator docstring


    【解决方案1】:

    发生这种情况是因为您传递的变量名称实际上被捕获到 func 参数中。

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

    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 函数是实际装饰器。总是这样传递:

    • 将装饰器参数传递给外部函数
    • 将原始函数传递给内部函数

    一旦 Python 看到这个:

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

    ...它会在后台执行类似的操作:

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

    ...扩展为:

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

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

    【讨论】:

      【解决方案2】:

      使用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
      

      【讨论】:

        猜你喜欢
        • 2022-12-28
        • 2014-07-21
        • 1970-01-01
        • 2014-10-14
        • 2012-03-03
        • 2014-03-24
        • 2015-10-19
        相关资源
        最近更新 更多