Python装饰器与选项

时间:2010-12-20 02:44:55

标签: python arguments decorator

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

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__方法返回函数调用的类,但我不确定如何传入这样的参数的语法。

这有意义吗?

4 个答案:

答案 0 :(得分:16)

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

新函数需要使用与您装饰的函数相同的参数,并且还需要调用原始函数。

现在,当你有一个带有参数的装饰器时,会有一个额外的级别。该装饰者应该接受参数,返回一个装饰者。您使用的功能实际上不是装饰器,而是装饰器制造者!

以下是一个例子:

>>> 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]

您将看到这些装饰器实现为类的示例。我也更喜欢它(尽管我通常最终都没有装饰器),但函数中函数的功能起作用。 :)

答案 1 :(得分:4)

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

的简写
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.'

顺便说一下,注意可变的默认参数(){};我确定你知道危险!

答案 2 :(得分:3)

正如其他答案所解释的那样,装饰器通常只通过使用它们的名称来调用一个隐式函数参数:

    @deco
    def somefunc(...): pass

与...相同:

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

这种情况下的参数是紧随其后的函数定义的编译版本。在这种情况下,装饰器返回一个callable,它被赋值给函数名而不是编译的函数体,正如通常情况那样。

但是,如果装饰器函数在调用时显式给出了一个或多个参数,如下所示:

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

它等同于:

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

正如您所看到的,这种情况下的工作方式有所不同。装饰器函数仍然返回一个可调用的,只有这次 期望一个“普通”单个隐式参数装饰器函数,然后使用以下函数定义中的函数对象调用它,就像之前一样。 / p>

再次 - 正如其他人所指出的那样 - 这使得装饰者明确地传递了参数,装饰工厂,因为他们构造并返回'常规'装饰函数。

在大多数情况下,即使不是所有情况,装饰器也可以作为函数或类实现,因为它们都可以在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

decorator所做的是定义在简单的“无参数”场景中修饰的函数会发生什么 - 这里定义并返回另一个 - 但最终 - 嵌套函数名为decorated,其中只需调用模块的do()函数,并从调用点传递参数(如果有的话),以及用于do()函数的参数。

由于外部装饰器和正在装饰的函数都具有关键字参数,因此这种用例有点复杂。需要格外小心以确保每个关键字的名称都是唯一的,这样它们就不会发生冲突(并且kwargs函数中的某些东西不会无意中更改可变do()参数的默认值

答案 3 :(得分:0)

我喜欢@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