我如何在Python中制作两个装饰器来完成以下操作?

@make_bold
@make_italic
def say():
   return "Hello"

调用say()应返回:

"<b><i>Hello</i></b>"

当前回答

当然,您也可以从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()

其他回答

装饰器接受函数定义并创建一个新函数,该函数执行该函数并转换结果。

@deco
def do():
    ...

相当于:

do = deco(do)

例子:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

This

@deco
def do(number):
    return chr(number)  # number to letter

相当于这个

def do2(number):
    return chr(number)

do2 = deco(do2)

65<=>“a”

print(do(65))
print(do2(65))
>>> B
>>> B

要理解decorator,需要注意的是,decorator创建了一个新的函数do,它在内部执行函数并转换结果。

查看文档以了解装饰器是如何工作的。以下是您的要求:

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapper

def makeitalic(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapper

@makebold
@makeitalic
def hello():
    return "hello world"

@makebold
@makeitalic
def log(s):
    return s

print hello()        # returns "<b><i>hello world</i></b>"
print hello.__name__ # with functools.wraps() this returns "hello"
print log('hello')   # returns "<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语句中,该方法应用于列表本身,而不是列表的元素。

这里是一个链接装饰器的简单示例。注意最后一行-它显示了封面下的情况。

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

您可以制作两个独立的装饰器,如下图所示。请注意,在wrapped()函数的声明中使用了*args,**kwargs,该函数支持具有多个参数的修饰函数(对于示例say()函数来说,这并不是真正必要的,但为了通用性,它包含在内)。

出于类似的原因,functools.wraps装饰器用于将包装函数的元属性更改为被装饰函数的元特性。这使得错误消息和嵌入函数文档(func.__doc__)成为修饰函数的错误消息,而不是wrapped()的错误消息。

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

精炼

正如您所看到的,这两个装饰器中有很多重复的代码。考虑到这种相似性,您最好创建一个实际上是装饰器工厂的通用函数,换句话说,一个生成其他装饰器的装饰器函数。这样可以减少代码重复,并允许遵循DRY原则。

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

为了使代码更可读,可以为工厂生成的装饰器指定一个更具描述性的名称:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

甚至可以这样组合:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

效率

虽然上面的示例都能完成所有工作,但当同时应用多个修饰符时,生成的代码会以外部函数调用的形式产生大量开销。这可能无关紧要,具体取决于确切的用法(例如,可能是I/O绑定的)。

如果修饰函数的速度很重要,那么可以通过编写一个稍微不同的修饰器工厂函数来保持单个额外函数调用的开销,该函数实现一次添加所有标记,从而可以生成代码,避免对每个标记使用单独的修饰器而导致的额外函数调用。

这需要在decorator本身中有更多的代码,但这只在将其应用于函数定义时运行,而不是在稍后调用它们本身时运行。这也适用于通过使用前面所示的lambda函数创建更可读的名称时。示例:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>