【问题标题】:Python decorator with options带有选项的 Python 装饰器
【发布时间】:2011-05-28 01:19:13
【问题描述】:

我有一个模块,它有一个原型类似于线程类的函数。

def do(fn, argtuple=(), kwargdict={}, priority=0,
            block=False, timeout=0, callback=None, daemon=False)

    # do stuff

fn 是一个可调用对象,而 argtuple 和 kwargdict 是位置参数和字典参数,将在调用时传递给 fn 可调用对象。

我现在正在尝试为此编写一个装饰器,但我很困惑。我从来没有真正很好地掌握装饰器。有没有办法制作一个装饰器,我可以设置上面的选项,例如超时,但在调用函数时传入 argtuple 和 kwargdict。

例如:

@do(priority=2)
def decoratedTask(arg, dic=3):
    #do stuff

decoratedTask(72)

我很困惑如何将运行时参数 72 传递给修饰函数。我认为装饰器需要是 __call__ 方法返回函数调用的类,但我不确定如何传入这样的参数的语法。

这有意义吗?

【问题讨论】:

    标签: python arguments decorator


    【解决方案1】:

    装饰器所做的是将函数作为参数并返回一个函数,通常是在装饰器中创建的新函数。

    那个新函数需要和你装饰的函数接受相同的参数,并且它还需要调用原函数。

    现在,当你有一个带参数的装饰器时,就会有一个额外的层次。该装饰器应该接受参数,并返回一个装饰器。你使用的函数其实不是装饰器,而是装饰器制造者!

    这是一个例子:

    >>> def mydeco(count):
    ...     def multipass(fn):
    ...         def caller(*args, **kw):
    ...             return [fn(*args, **kw) for x in range(count)]
    ...         return caller
    ...     return multipass
    ... 
    >>> @mydeco(5)
    ... def printer(text):
    ...     print(text)
    ... 
    >>> printer("Yabbadabbadoo!")
    Yabbadabbadoo!
    Yabbadabbadoo!
    Yabbadabbadoo!
    Yabbadabbadoo!
    Yabbadabbadoo!
    [None, None, None, None, None]
    

    您将看到这些装饰器制造商作为类实现的示例。这也是我更喜欢它的方式(尽管我通常最终根本没有装饰器),但是函数中的函数可以工作。 :)

    【讨论】:

    • 谢谢,这让它更清楚了。我只是对这样的深层嵌套函数定义感到困惑。我很确定我明白了。在我接受之前让我玩一下。
    【解决方案2】:

    装饰器语法的工作原理并不完全如此。当您编写 @do(priority=2) 时,Python 将评估 do(priority=2) 并使用该调用的 result 作为装饰器。它是

    的简写
    decorator=do(priority=2)
    @decorator
    

    所以你实际上想让do 成为一个装饰器工厂:你希望它接受所有位置参数并返回一个装饰器。

    def do(args=(), kwargs={}, ...):
        def _decorator(fn):
            def newfn(*args, **kwargs):
                return fn(*args, **kwargs)
            return newfn
        return _decorator
    

    注意这里实际上有三个函数!

    • do 是我们调用的函数来获取我们的装饰器(例如,do(priority=2) 是一个示例装饰器)
    • _decorator 是实际返回的装饰器,这取决于 do 的参数
    • 由于装饰器接受一个函数作为输入并返回一个函数作为输出,我们需要定义装饰器返回的函数。 newfn 就是那个功能。

    例子:

    >>> def rename(name):
    ...     def _decorator(fn):
    ...             def renamed(*args, **kwargs):
    ...                     return fn(*args, **kwargs)
    ...             renamed.__name__ = name
    ...             return renamed
    ...     return _decorator
    ...
    >>> @rename('I like omelettes in the morning.')
    ... def foo():
    ...     return 'bar'
    ...
    >>> foo()
    'bar'
    >>> foo.__name__
    'I like omelettes in the morning.'
    

    或等效

    >>> omeletter = rename('I like omelettes in the morning.')
    >>> @omeletter
    ... def foo():
    ...     return 'bar'
    ...
    >>> foo()
    'bar'
    >>> foo.__name__
    'I like omelettes in the morning.'
    

    顺便说一句,注意可变的默认参数(){};我相信你知道其中的危险!

    【讨论】:

    • katrielalex: () 不是可变的,默认情况下就可以了。 {} 是对的。
    • @Duncan -- 确实如此!谢谢=)
    • 你能指出解释这种行为的文章或文档吗?我真的不明白函数参数是如何在 python 中传递的,这在我看来有点不合逻辑。
    【解决方案3】:

    正如其他答案所解释的,装饰器在调用时通常会传递一个隐式函数参数,只需使用它们的名称,如下所示:

        @deco
        def somefunc(...): pass
    

    与以下内容相同:

        def somefunc(...): pass
        somefunc = deco(somefunc)
    

    这种情况下的参数是紧随其后的函数定义的编译版本。在这种情况下,装饰器返回一个分配给函数名称的可调用对象,而不是通常情况下的编译函数体。

    但是,如果一个装饰器函数在被调用时被显式地赋予一个或多个参数,像这样:

        @deco(args)
        def somefunc(...): pass
    

    变成等价于:

        def somefunc(...): pass
        somefunc = deco(args)(somefunc)
    

    如您所见,这种情况下的工作方式有所不同。装饰器函数仍然返回一个可调用的,只是这一次 that 需要一个“普通的”单个隐式参数装饰器函数,然后像以前一样使用来自以下函数定义的函数对象调用。

    再次——正如其他人所指出的——这使得装饰器显式传递参数,装饰器工厂在某种意义上他们构造并返回“常规”装饰器函数。

    在大多数情况下,装饰器可以实现为函数或类,因为两者都可以在 Python 中调用。就个人而言,我发现函数更容易理解,因此将在下面使用该方法。另一方面,函数方法可能会变得很棘手,因为它通常涉及一个或多个嵌套函数定义。

    以下是如何为模块中的do()函数编写装饰器。在下面的代码中,我定义了它的作用是在调用函数之前打印出函数的参数。

    def do(fn, args=tuple(), kwargs={}, priority=0,
           block=False, timeout=0, callback=None, daemon=False):
        # show arguments
        print ('in do(): fn={!r}, args={}, kwargs={}, priority={},\n'
               '         block={}, timeout={}, callback={}, daemon={}'
               .format(fn.__name__, args, kwargs, priority,
                       block, timeout, callback, daemon))
        # and call function 'fn' with its arguments
        print ('  calling {}({}, {})'.format(
                   fn.__name__,
                   ', '.join(map(str, args)) if args else '',
                   ', '.join('{}={}'.format(k, v) for k,v in kwargs.items())
                        if kwargs else '')
              )
        fn(*args, **kwargs)
    
    def do_decorator(**do_kwargs):
        def decorator(fn):
            def decorated(*args, **kwargs):
                do(fn, args, kwargs, **do_kwargs)
            return decorated
        return decorator
    
    @do_decorator(priority=2)
    def decoratedTask(arg, dic=42):
        print 'in decoratedTask(): arg={}, dic={}'.format(arg, dic)
    
    decoratedTask(72, dic=3)
    

    输出:

    in do(): fn='decoratedTask', args=(72,), kwargs={'dic': 42}, priority=2,
             block=False, timeout=0, callback=None, daemon=False
      calling decoratedTask(72, dic=3)
    in decoratedTask(): arg=72, dic=3
    

    以下是这个看起来很复杂的东西如何工作的详细说明:

    外部装饰器函数do_decorator(),定义了它返回的另一个内部装饰器函数,这里创造性地命名为decorator

    Whatdecoratordoes 定义了在简单的“无参数”场景中修饰的函数会发生什么——这里定义并返回另一个——但最终的——嵌套函数decorated,它只调用模块的do()function 并从调用点传递两个参数(如果有的话),以及那些供do()function 使用的参数。

    这个用例有点复杂,因为外部装饰器和被装饰的函数都有关键字参数。需要特别注意确保每个关键字的名称都是唯一的,以免它们发生冲突(并且 mutablekwargsargument 默认值不会被do()函数中的某些内容无意更改)。

    【讨论】:

      【解决方案4】:

      我喜欢上面的@Lennart Regebro、@katrielalex 和@martineau 的回答,但是冒着听起来非常老套的风险,我会冒险写一个基于故事的示例。

      在这两个部分的故事中,我们可以让教师从经验中教授学生

      def self_taught_teacher(fn):
          ''' I teach students, because I had no teacher to guide me.'''
          def a_student(*real_life_issues, **details_about_my_world):
              ''' I'm a student who has been trained by a teacher 
              who has taught me about the problems I may encounter in real life.
      
              Thanks to my teacher for giving me extra knowledge.    
              '''
              print 'I have to remember what my teacher taught me.'
              my_answer = fn(*real_life_issues, **details_about_my_world)
              print 'Ah yes, I made the right decision.'
              #
              return my_answer
          #
          return a_student
      
      @self_taught_teacher
      def student_named_Lisa_practicing_maths(length, width, height):
          print 'Im consulting my powers of maths...'
          return length * width * height
      

      让我们看看学生都知道什么......

      >>> answer = student_named_Lisa_practicing_maths(10, 20, height=3)
      I have to remember what my teacher taught me.
      Im consulting my powers of maths...
      Ah yes, I made the right decision.
      >>> answer
      600
      

      非常好。在故事的第二部分中,我们介绍了一位教授,他教别人成为教师。然后那些教师与他们的学生分享他们学到的东西。

      def professor_who_trains_teachers(*subjects, **to_train_teachers):
          '''I am a profeseur. I help train teachers.  ''' 
          #
          def a_teacher_who_gets_trained(fn): 
              ''' I learn subjects I should teach to my students.'''
              knowledge = [s for s in subjects]
              #
              def a_student(*real_life_issues, **details_about_my_world):
                  ''' I'm a student who has been trained by a teacher 
                  who has taught me about the problems I may encounter in real life.
      
                  Thanks to my teacher for giving me extra knowledge.    
                  '''
                  print '(I know %s that i learned from my teacher,...)' % \
                          [information for information in knowledge]
                  my_answer = fn(*real_life_issues, **details_about_my_world)
                  print 'Ah yes, I made the right decision.'
                  #
                  return my_answer
              #
              return a_student
              #
              #
          return a_teacher_who_gets_trained 
      

      所以我们可以培训一个老师,让他们教一个学生......

      >>> teacher1 = professor_who_trains_teachers('math','science')
      >>> teacher1
      <function a_teacher_who_gets_trained at 0x104a7f500>
      >>> teacher1.__name__
      'a_teacher_who_gets_trained'
      >>> 
      
      @teacher1
      def student_named_Lisa_practicing_maths(length, width, height):
          print 'Im consulting my powers of maths...'
          return length * width * height
      

      那个学生知道他们的数学......

      >>> answer = student_named_Lisa_practicing_maths(20, 10, 2)
      (I know ['math', 'science'] that i learned from my teacher,...)
      Im consulting my powers of maths...
      Ah yes, I made the right decision.
      >>> 
      >>> answer
      400
      

      我们也可以直接创建教师。

      @professor_who_trains_teachers('math', 'science', remember='patience')
      def student_named_Lisa_practicing_maths(length, width, height):
          print 'Im consulting my powers of maths...'
          return length * width * height
      

      新生又可以做数学了……

      >>> answer = student_named_Lisa_practicing_maths(10, 20, height=3)
      (I know ['math', 'science'] that i learned from my teacher,...)
      Im consulting my powers of maths...
      Ah yes, I made the right decision.
      >>> 
      >>> answer
      600
      

      【讨论】:

        猜你喜欢
        • 2014-02-16
        • 2014-07-21
        • 1970-01-01
        • 2014-10-04
        • 2018-08-20
        • 2015-01-25
        • 2018-11-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多