我有一个模块,它有一个函数,其原型类似于线程类的原型。
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__
方法返回函数调用的类,但我不确定如何传入这样的参数的语法。
这有意义吗?
答案 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