我如何在Python中制作两个装饰器来完成以下操作?
@make_bold
@make_italic
def say():
return "Hello"
调用say()应返回:
"<b><i>Hello</i></b>"
我如何在Python中制作两个装饰器来完成以下操作?
@make_bold
@make_italic
def say():
return "Hello"
调用say()应返回:
"<b><i>Hello</i></b>"
当前回答
考虑下面的修饰符,注意我们将wrapper()函数作为对象返回
def make_bold(func):
def wrapper():
return '<b>'+func()+'</b>'
return wrapper
所以这个
@make_bold
def say():
return "Hello"
计算结果为
x = make_bold(say)
注意,x不是say(),而是在内部调用say(()的包装器对象。这就是装饰师的工作原理。它总是返回调用实际函数的包装器对象。如果链接此
@make_italic
@make_bold
def say():
return "Hello"
转换为此
x = make_bold(say)
y = make_italic(x)
以下是完整的代码
def make_italic(func):
def wrapper():
return '<i>'+func()+'</i>'
return wrapper
def make_bold(func):
def wrapper():
return '<b>'+func()+'</b>'
return wrapper
@make_italic
@make_bold
def say():
return "Hello"
if __name__ == '__main__':
# x = make_bold(say) When you wrap say with make_bold decorator
# y = make_italic(x) When you also add make_italic as part of chaining
# print(y())
print(say())
上述代码将返回
<i><b>Hello</b></i>
希望这有帮助
其他回答
这里是一个链接装饰器的简单示例。注意最后一行-它显示了封面下的情况。
############################################################
#
# decorators
#
############################################################
def bold(fn):
def decorate():
# surround with bold tags before calling original function
return "<b>" + fn() + "</b>"
return decorate
def uk(fn):
def decorate():
# swap month and day
fields = fn().split('/')
date = fields[1] + "/" + fields[0] + "/" + fields[2]
return date
return decorate
import datetime
def getDate():
now = datetime.datetime.now()
return "%d/%d/%d" % (now.day, now.month, now.year)
@bold
def getBoldDate():
return getDate()
@uk
def getUkDate():
return getDate()
@bold
@uk
def getBoldUkDate():
return getDate()
print getDate()
print getBoldDate()
print getUkDate()
print getBoldUkDate()
# what is happening under the covers
print bold(uk(getDate))()
输出如下所示:
17/6/2013
<b>17/6/2013</b>
6/17/2013
<b>6/17/2013</b>
<b>6/17/2013</b>
考虑下面的修饰符,注意我们将wrapper()函数作为对象返回
def make_bold(func):
def wrapper():
return '<b>'+func()+'</b>'
return wrapper
所以这个
@make_bold
def say():
return "Hello"
计算结果为
x = make_bold(say)
注意,x不是say(),而是在内部调用say(()的包装器对象。这就是装饰师的工作原理。它总是返回调用实际函数的包装器对象。如果链接此
@make_italic
@make_bold
def say():
return "Hello"
转换为此
x = make_bold(say)
y = make_italic(x)
以下是完整的代码
def make_italic(func):
def wrapper():
return '<i>'+func()+'</i>'
return wrapper
def make_bold(func):
def wrapper():
return '<b>'+func()+'</b>'
return wrapper
@make_italic
@make_bold
def say():
return "Hello"
if __name__ == '__main__':
# x = make_bold(say) When you wrap say with make_bold decorator
# y = make_italic(x) When you also add make_italic as part of chaining
# print(y())
print(say())
上述代码将返回
<i><b>Hello</b></i>
希望这有帮助
Paolo Bergan蒂诺的答案具有只使用stdlib的巨大优势,适用于这个简单的示例,其中既没有修饰器参数,也没有修饰函数参数。
然而,如果您想处理更一般的情况,它有三个主要限制:
正如在几个答案中已经指出的,您不能轻易地修改代码以添加可选的修饰符参数。例如,创建makestyle(style='bold')装饰器非常简单。此外,使用@functools.wraps创建的包装器不保留签名,因此如果提供了错误的参数,它们将开始执行,并且可能引发与通常的TypeError不同的错误。最后,在使用@functools.wraps创建的包装器中,很难根据其名称访问参数。事实上,参数可以出现在*args、**kwargs中,也可以根本不出现(如果是可选的)。
我写了decopatch来解决第一个问题,写了makefun.wraps来解决另外两个问题。注意,makefun利用了与著名的decorator lib相同的技巧。
这是如何创建带有参数的装饰器,返回真正的签名保护包装器:
from decopatch import function_decorator, DECORATED
from makefun import wraps
@function_decorator
def makestyle(st='b', fn=DECORATED):
open_tag = "<%s>" % st
close_tag = "</%s>" % st
@wraps(fn)
def wrapped(*args, **kwargs):
return open_tag + fn(*args, **kwargs) + close_tag
return wrapped
decopatch为您提供了其他两种开发样式,根据您的喜好,隐藏或显示各种python概念。最紧凑的样式如下:
from decopatch import function_decorator, WRAPPED, F_ARGS, F_KWARGS
@function_decorator
def makestyle(st='b', fn=WRAPPED, f_args=F_ARGS, f_kwargs=F_KWARGS):
open_tag = "<%s>" % st
close_tag = "</%s>" % st
return open_tag + fn(*f_args, **f_kwargs) + close_tag
在这两种情况下,您都可以检查装饰器是否按预期工作:
@makestyle
@makestyle('i')
def hello(who):
return "hello %s" % who
assert hello('world') == '<b><i>hello world</i></b>'
有关详细信息,请参阅文档。
当您需要在decorator中添加自定义参数时,我会添加一个案例,将其传递给最终函数,然后使用它。
装饰师:
def jwt_or_redirect(fn):
@wraps(fn)
def decorator(*args, **kwargs):
...
return fn(*args, **kwargs)
return decorator
def jwt_refresh(fn):
@wraps(fn)
def decorator(*args, **kwargs):
...
new_kwargs = {'refreshed_jwt': 'xxxxx-xxxxxx'}
new_kwargs.update(kwargs)
return fn(*args, **new_kwargs)
return decorator
以及最终功能:
@app.route('/')
@jwt_or_redirect
@jwt_refresh
def home_page(*args, **kwargs):
return kwargs['refreched_jwt']
这个答案早就有了答案,但我想我会分享我的Decorator类,这使编写新的Decorator变得简单而紧凑。
from abc import ABCMeta, abstractclassmethod
class Decorator(metaclass=ABCMeta):
""" Acts as a base class for all decorators """
def __init__(self):
self.method = None
def __call__(self, method):
self.method = method
return self.call
@abstractclassmethod
def call(self, *args, **kwargs):
return self.method(*args, **kwargs)
首先,我认为这使装饰器的行为非常清晰,但也使定义新的装饰器变得非常简洁。对于上面列出的示例,您可以将其解为:
class MakeBold(Decorator):
def call():
return "<b>" + self.method() + "</b>"
class MakeItalic(Decorator):
def call():
return "<i>" + self.method() + "</i>"
@MakeBold()
@MakeItalic()
def say():
return "Hello"
您也可以使用它来执行更复杂的任务,例如,一个装饰器,它会自动将函数递归地应用于迭代器中的所有参数:
class ApplyRecursive(Decorator):
def __init__(self, *types):
super().__init__()
if not len(types):
types = (dict, list, tuple, set)
self._types = types
def call(self, arg):
if dict in self._types and isinstance(arg, dict):
return {key: self.call(value) for key, value in arg.items()}
if set in self._types and isinstance(arg, set):
return set(self.call(value) for value in arg)
if tuple in self._types and isinstance(arg, tuple):
return tuple(self.call(value) for value in arg)
if list in self._types and isinstance(arg, list):
return list(self.call(value) for value in arg)
return self.method(arg)
@ApplyRecursive(tuple, set, dict)
def double(arg):
return 2*arg
print(double(1))
print(double({'a': 1, 'b': 2}))
print(double({1, 2, 3}))
print(double((1, 2, 3, 4)))
print(double([1, 2, 3, 4, 5]))
哪些打印:
2
{'a': 2, 'b': 4}
{2, 4, 6}
(2, 4, 6, 8)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
注意,这个示例没有在decorator的实例化中包含列表类型,因此在最终的print语句中,该方法应用于列表本身,而不是列表的元素。