我如何在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>"
当前回答
Python装饰器为另一个函数添加了额外的功能
斜体装饰符可以如下所示
def makeitalic(fn):
def newFunc():
return "<i>" + fn() + "</i>"
return newFunc
注意,函数是在函数内部定义的。它基本上是用新定义的函数替换函数。例如,我有这门课
class foo:
def bar(self):
print "hi"
def foobar(self):
print "hi again"
现在,我希望两个函数在完成后和完成前都打印“---”。我可以在每个打印语句前后添加一个打印“---”。但因为我不喜欢重复自己,我会做一个装饰师
def addDashes(fn): # notice it takes a function as an argument
def newFunction(self): # define a new function
print "---"
fn(self) # call the original function
print "---"
return newFunction
# Return the newly defined function - it will "replace" the original
所以现在我可以把我的班级改成
class foo:
@addDashes
def bar(self):
print "hi"
@addDashes
def foobar(self):
print "hi again"
有关装饰器的详细信息,请查看http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
其他回答
做同样事情的另一种方式:
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'
装饰只是语法上的糖。
This
@decorator
def func():
...
扩展到
def func():
...
func = decorator(func)
当然,您也可以从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()
当您需要在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']
您可以制作两个独立的装饰器,如下图所示。请注意,在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>