我如何在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>"
当前回答
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>'
有关详细信息,请参阅文档。
其他回答
我如何在Python中制作两个装饰器来完成以下操作?
调用时需要以下函数:
@马克博尔德@使倾斜def say():return“您好”
要返回:
<b><i> 你好</i></b>
简单的解决方案
为了最简单地做到这一点,请制作返回lambdas(匿名函数)的装饰器,这些函数在函数(闭包)上关闭并调用它:
def makeitalic(fn):
return lambda: '<i>' + fn() + '</i>'
def makebold(fn):
return lambda: '<b>' + fn() + '</b>'
现在根据需要使用它们:
@makebold
@makeitalic
def say():
return 'Hello'
现在:
>>> say()
'<b><i>Hello</i></b>'
简单解决方案的问题
但我们似乎几乎失去了最初的功能。
>>> say
<function <lambda> at 0x4ACFA070>
要找到它,我们需要深入研究每个lambda的闭包,其中一个被另一个所掩埋:
>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>
因此,如果我们将文档放在这个函数上,或者希望能够修饰包含多个参数的函数,或者我们只是想知道在调试会话中看到的是什么函数,那么我们需要对包装器做更多的工作。
全功能解决方案-克服大多数这些问题
我们在标准库中有来自functools模块的修饰符包装!
from functools import wraps
def makeitalic(fn):
# must assign/update attributes from wrapped function to wrapper
# __module__, __name__, __doc__, and __dict__ by default
@wraps(fn) # explicitly give function whose attributes it is applying
def wrapped(*args, **kwargs):
return '<i>' + fn(*args, **kwargs) + '</i>'
return wrapped
def makebold(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return '<b>' + fn(*args, **kwargs) + '</b>'
return wrapped
不幸的是,仍然有一些样板,但这是我们所能做到的最简单的。
在Python3中,默认情况下还会分配__qualiname__和__annotations__。
现在:
@makebold
@makeitalic
def say():
"""This function returns a bolded, italicized 'hello'"""
return 'Hello'
现在:
>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:
say(*args, **kwargs)
This function returns a bolded, italicized 'hello'
结论
所以我们看到,包装使包装函数几乎可以做所有的事情,除了告诉我们该函数将什么作为参数。
还有其他模块可以尝试解决这个问题,但标准库中还没有解决方案。
下面有make_bold()和make_italic():
def make_bold(func):
def core(*args, **kwargs):
result = func(*args, **kwargs)
return "<b>" + result + "</b>"
return core
def make_italic(func):
def core(*args, **kwargs):
result = func(*args, **kwargs)
return "<i>" + result + "</i>"
return core
您可以使用say()将它们用作装饰器,如下所示:
@make_bold
@make_italic
def say():
return "Hello"
print(say())
输出:
<b><i>Hello</i></b>
当然,您可以直接使用make_bold()和make_italic()而不使用修饰符,如下所示:
def say():
return "Hello"
f1 = make_italic(say)
f2 = make_bold(f1)
result = f2()
print(result)
简而言之:
def say():
return "Hello"
result = make_bold(make_italic(say))()
print(result)
输出:
<b><i>Hello</i></b>
做同样事情的另一种方式:
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'
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
考虑下面的修饰符,注意我们将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>
希望这有帮助