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

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

调用say()应返回:

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

当前回答

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

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

其他回答

说到计数器示例-如上所述,计数器将在使用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)),并且计数器变量将对每个变量保持私有。

做同样事情的另一种方式:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

或者,更灵活地说:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

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

有关详细信息,请参阅文档。

或者,您可以编写一个工厂函数,该函数返回一个装饰器,该装饰器将装饰函数的返回值包装在传递给工厂函数的标记中。例如:

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

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

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