我如何在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>"
当前回答
这个答案早就有了答案,但我想我会分享我的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语句中,该方法应用于列表本身,而不是列表的元素。
其他回答
当然,您也可以从decorator函数返回lambdas:
def makebold(f):
return lambda: "<b>" + f() + "</b>"
def makeitalic(f):
return lambda: "<i>" + f() + "</i>"
@makebold
@makeitalic
def say():
return "Hello"
print say()
考虑下面的修饰符,注意我们将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>
希望这有帮助
或者,您可以编写一个工厂函数,该函数返回一个装饰器,该装饰器将装饰函数的返回值包装在传递给工厂函数的标记中。例如:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator():
return '<%(tag)s>%(rv)s</%(tag)s>' % (
{'tag': tag, 'rv': func()})
return decorator
return factory
这使您能够编写:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
return 'hello'
or
makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')
@makebold
@makeitalic
def say():
return 'hello'
就我个人而言,我会用不同的方式来编写装饰器:
from functools import wraps
def wrap_in_tag(tag):
def factory(func):
@wraps(func)
def decorator(val):
return func('<%(tag)s>%(val)s</%(tag)s>' %
{'tag': tag, 'val': val})
return decorator
return factory
这将产生:
@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
return val
say('hello')
不要忘了decorator语法是一种简写的构造:
say = wrap_in_tag('b')(wrap_in_tag('i')(say)))
用于绘制图像的嵌套装饰器的又一示例:
import matplotlib.pylab as plt
def remove_axis(func):
def inner(img, alpha):
plt.axis('off')
func(img, alpha)
return inner
def plot_gray(func):
def inner(img, alpha):
plt.gray()
func(img, alpha)
return inner
@remove_axis
@plot_gray
def plot_image(img, alpha):
plt.imshow(img, alpha=alpha)
plt.show()
现在,让我们先使用嵌套的装饰器显示一个没有轴标签的彩色图像:
plot_image(plt.imread('lena_color.jpg'), 0.4)
接下来,让我们使用嵌套的装饰器remove_axis和plot_gray显示一个没有轴标签的灰度图像(我们需要cmap='gray',否则默认的颜色映射是viridis,因此除非明确指定,否则默认情况下灰度图像不会以黑白色显示)
plot_image(plt.imread('lena_bw.jpg'), 0.8)
上述函数调用缩减为以下嵌套调用
remove_axis(plot_gray(plot_image))(img, alpha)
这里是一个链接装饰器的简单示例。注意最后一行-它显示了封面下的情况。
############################################################
#
# 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>