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

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

调用say()应返回:

"<b><i>Hello</i></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>

其他回答

考虑下面的修饰符,注意我们将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>

希望这有帮助

说到计数器示例-如上所述,计数器将在使用decorator的所有函数之间共享:

def counter(func):
    def wrapped(*args, **kws):
        print 'Called #%i' % wrapped.count
        wrapped.count += 1
        return func(*args, **kws)
    wrapped.count = 0
    return wrapped

这样,您的装饰器可以重复用于不同的函数(或用于多次装饰同一个函数:func_counter1=counter(func);func_counter2=counter(func)),并且计数器变量将对每个变量保持私有。

这个答案早就有了答案,但我想我会分享我的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语句中,该方法应用于列表本身,而不是列表的元素。

装饰只是语法上的糖。

This

@decorator
def func():
    ...

扩展到

def func():
    ...
func = decorator(func)

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

@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,它在内部执行函数并转换结果。