如何制作函数装饰器并将它们链接在一起?
如何在 Python 中制作两个能够执行以下操作的装饰器?
@make_bold
@make_italic
def say():
return "Hello"
调用 say() 应该返回:
"<b><i>Hello</i></b>"
How do I make two decorators in Python that would do the following?
@make_bold
@make_italic
def say():
return "Hello"
Calling say()
should return:
"<b><i>Hello</i></b>"
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(22)
如果您不喜欢冗长的解释,请参阅Paolo Bergantino 的回答。
装饰器基础知识
Python 的函数是对象
要理解装饰器,您必须首先了解 Python 中的函数是对象。 这会产生重要的后果。 让我们用一个简单的例子来看看为什么:
记住这一点。 我们很快就会回到这个话题。
Python 函数的另一个有趣的属性是它们可以在另一个函数中定义!
函数参考
好的,还在吗? 现在有趣的部分...
您已经看到函数是对象。 因此,函数:
这意味着一个函数可以
返回
另一个函数。还有更多!
如果您可以
返回
一个函数,您就可以将一个函数作为参数传递:嗯,您已经拥有了理解装饰器所需的一切。 您会看到,装饰器是“包装器”,这意味着它们让您可以在它们装饰的函数之前和之后执行代码,而无需修改函数本身。
手工制作的装饰器
如何手动完成:
现在,您可能希望每次调用
a_stand_alone_function
时,都调用a_stand_alone_function_decorated
。 这很简单,只需用my_shiny_new_decorator
返回的函数覆盖a_stand_alone_function
即可:装饰器揭秘
前面的示例,使用装饰器语法:
是的,就是这样,就是这么简单。
@decorator
只是一个快捷方式:装饰器只是 装饰器设计的 Python 变体模式。 Python 中嵌入了几种经典的设计模式来简化开发(例如迭代器)。
当然,您可以积累装饰器:
使用Python装饰器语法:
设置装饰器的顺序很重要:
现在:回答问题...
作为结论,您可以轻松了解如何回答问题:
您现在可以离开高兴吧,或者多花点时间看看装饰器的高级用法。
将装饰器提升到一个新的水平
将参数传递给被装饰函数
装饰方法
Python 的一个妙处是方法和函数实际上是相同的。 唯一的区别是方法期望它们的第一个参数是对当前对象 (
self
) 的引用。这意味着您可以以相同的方式为方法构建装饰器! 请记住考虑
self
:如果您正在制作通用装饰器(您将应用于任何函数或方法,无论其参数如何),那么只需使用
* args,**kwargs
:将参数传递给装饰器
很好,现在您对将参数传递给装饰器本身有何看法?
这可能会有些扭曲,因为装饰器必须接受函数作为参数。 因此,您不能将装饰函数的参数直接传递给装饰器。
在急于解决问题之前,我们先写一个小提示:
完全一样。 调用“
my_decorator
”。 因此,当您@my_decorator
时,您是在告诉Python调用“由变量“my_decorator
”标记的函数”。这个很重要! 您给出的标签可以直接指向装饰器——或不指向。
让我们变得邪恶吧。 ☺
这并不奇怪。
让我们做完全相同的事情,但跳过所有讨厌的中间变量:
让我们让它更短:
嘿,你看到了吗? 我们使用了带有“
@
”语法的函数调用! :-)那么,回到带有参数的装饰器。 如果我们可以使用函数来动态生成装饰器,我们就可以将参数传递给该函数,对吧?
这是:一个带有参数的装饰器。 参数可以设置为变量:
如您所见,您可以像使用此技巧的任何函数一样将参数传递给装饰器。 如果您愿意,您甚至可以使用
*args, **kwargs
。 但请记住,装饰器仅被调用一次。 就在Python导入脚本的时候。 之后您无法动态设置参数。 当你执行“import x”时,函数已经被修饰,所以你不能改变任何事情。
让我们练习一下:装饰一个装饰器
好吧,作为奖励,我会给你一个片段,让任何装饰器普遍接受任何参数。 毕竟,为了接受参数,我们使用另一个函数创建了装饰器。
我们包裹了装饰器。
我们最近还看到过其他什么包装函数吗?
哦,是的,装饰者!
让我们玩得开心,为装饰器写一个装饰器:
它可以这样使用:
我知道,上次你有这种感觉,是在听一个家伙说:“在了解递归之前,你必须先了解递归”之后。 但现在,掌握了这个你不感觉很好吗?
最佳实践:装饰器
functools 模块是在 Python 2.5 中引入的。 它包括函数 functools.wraps() ,它将修饰函数的名称、模块和文档字符串复制到其包装器。
(有趣的事实:
functools.wraps()
是一个装饰器!☺)装饰器有什么用处?
现在是大问题:我可以使用装饰器做什么?
看起来很酷而且很强大,但如果有一个实际的例子就更好了。 嗯,有 1000 种可能性。 经典用途是从外部库扩展函数行为(您无法修改它),或用于调试(您不想修改它,因为它是临时的)。
您可以使用它们以 DRY 的方式扩展多个函数,如下所示:
当然,装饰器的好处是您可以立即在几乎任何东西上使用它们,而无需重写。 DRY,我说:
Python本身提供了几个装饰器:
property
、staticmethod
等。Django这确实是一个大游乐场。
If you are not into long explanations, see Paolo Bergantino’s answer.
Decorator Basics
Python’s functions are objects
To understand decorators, you must first understand that functions are objects in Python. This has important consequences. Let’s see why with a simple example :
Keep this in mind. We’ll circle back to it shortly.
Another interesting property of Python functions is they can be defined inside another function!
Functions references
Okay, still here? Now the fun part...
You’ve seen that functions are objects. Therefore, functions:
That means that a function can
return
another function.There’s more!
If you can
return
a function, you can pass one as a parameter:Well, you just have everything needed to understand decorators. You see, decorators are “wrappers”, which means that they let you execute code before and after the function they decorate without modifying the function itself.
Handcrafted decorators
How you’d do it manually:
Now, you probably want that every time you call
a_stand_alone_function
,a_stand_alone_function_decorated
is called instead. That’s easy, just overwritea_stand_alone_function
with the function returned bymy_shiny_new_decorator
:Decorators demystified
The previous example, using the decorator syntax:
Yes, that’s all, it’s that simple.
@decorator
is just a shortcut to:Decorators are just a pythonic variant of the decorator design pattern. There are several classic design patterns embedded in Python to ease development (like iterators).
Of course, you can accumulate decorators:
Using the Python decorator syntax:
The order you set the decorators MATTERS:
Now: to answer the question...
As a conclusion, you can easily see how to answer the question:
You can now just leave happy, or burn your brain a little bit more and see advanced uses of decorators.
Taking decorators to the next level
Passing arguments to the decorated function
Decorating methods
One nifty thing about Python is that methods and functions are really the same. The only difference is that methods expect that their first argument is a reference to the current object (
self
).That means you can build a decorator for methods the same way! Just remember to take
self
into consideration:If you’re making general-purpose decorator--one you’ll apply to any function or method, no matter its arguments--then just use
*args, **kwargs
:Passing arguments to the decorator
Great, now what would you say about passing arguments to the decorator itself?
This can get somewhat twisted, since a decorator must accept a function as an argument. Therefore, you cannot pass the decorated function’s arguments directly to the decorator.
Before rushing to the solution, let’s write a little reminder:
It’s exactly the same. "
my_decorator
" is called. So when you@my_decorator
, you are telling Python to call the function 'labelled by the variable "my_decorator
"'.This is important! The label you give can point directly to the decorator—or not.
Let’s get evil. ☺
No surprise here.
Let’s do EXACTLY the same thing, but skip all the pesky intermediate variables:
Let’s make it even shorter:
Hey, did you see that? We used a function call with the "
@
" syntax! :-)So, back to decorators with arguments. If we can use functions to generate the decorator on the fly, we can pass arguments to that function, right?
Here it is: a decorator with arguments. Arguments can be set as variable:
As you can see, you can pass arguments to the decorator like any function using this trick. You can even use
*args, **kwargs
if you wish. But remember decorators are called only once. Just when Python imports the script. You can't dynamically set the arguments afterwards. When you do "import x", the function is already decorated, so you can'tchange anything.
Let’s practice: decorating a decorator
Okay, as a bonus, I'll give you a snippet to make any decorator accept generically any argument. After all, in order to accept arguments, we created our decorator using another function.
We wrapped the decorator.
Anything else we saw recently that wrapped function?
Oh yes, decorators!
Let’s have some fun and write a decorator for the decorators:
It can be used as follows:
I know, the last time you had this feeling, it was after listening a guy saying: "before understanding recursion, you must first understand recursion". But now, don't you feel good about mastering this?
Best practices: decorators
The
functools
module was introduced in Python 2.5. It includes the functionfunctools.wraps()
, which copies the name, module, and docstring of the decorated function to its wrapper.(Fun fact:
functools.wraps()
is a decorator! ☺)How can the decorators be useful?
Now the big question: What can I use decorators for?
Seem cool and powerful, but a practical example would be great. Well, there are 1000 possibilities. Classic uses are extending a function behavior from an external lib (you can't modify it), or for debugging (you don't want to modify it because it’s temporary).
You can use them to extend several functions in a DRY’s way, like so:
Of course the good thing with decorators is that you can use them right away on almost anything without rewriting. DRY, I said:
Python itself provides several decorators:
property
,staticmethod
, etc.This really is a large playground.
查看文档了解装饰器的工作原理。 这是您所要求的:
Check out the documentation to see how decorators work. Here is what you asked for:
或者,您可以编写一个返回装饰器的工厂函数,该装饰器将装饰函数的返回值包装在传递给工厂函数的标记中。 例如:
这使您可以编写:
或者就
我个人而言,我会以不同的方式编写装饰器:
这会产生:
不要忘记装饰器语法是简写的构造:
Alternatively, you could write a factory function which return a decorator which wraps the return value of the decorated function in a tag passed to the factory function. For example:
This enables you to write:
or
Personally I would have written the decorator somewhat differently:
which would yield:
Don't forget the construction for which decorator syntax is a shorthand:
装饰器只是语法糖。
这
扩展到
Decorators are just syntactical sugar.
This
expands to
当然,您也可以从装饰器函数返回 lambda:
And of course you can return lambdas as well from a decorator function:
Python 装饰器向另一个函数添加额外的功能
斜体装饰器可以像
注意 函数是在函数内部定义的。
它基本上所做的就是用新定义的函数替换函数。 例如,我有这个类,
现在说,我希望两个函数在完成之后和之前都打印“---”。
我可以在每个打印语句之前和之后添加一个打印“---”。
但是因为我不喜欢重复自己,所以我将制作一个装饰器
所以现在我可以将我的类更改为
有关装饰器的更多信息,请检查
http://www.ibm.com/developerworks/linux/library/l -cpdecor.html
Python decorators add extra functionality to another function
An italics decorator could be like
Note that a function is defined inside a function.
What it basically does is replace a function with the newly defined one. For example, I have this class
Now say, I want both functions to print "---" after and before they are done.
I could add a print "---" before and after each print statement.
But because I don't like repeating myself, I will make a decorator
So now I can change my class to
For more on decorators, check
http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
您可以创建两个独立的装饰器来执行您想要的操作,如下所示。 请注意,在
wrapped()
函数的声明中使用了*args, **kwargs
,该函数支持具有多个参数的修饰函数(对于例如say()
函数,但为了通用性而包含在内)。出于类似的原因,
functools.wraps
装饰器用于将包装函数的元属性更改为被装饰函数的元属性。 这使得错误消息和嵌入式函数文档 (func.__doc__
) 成为装饰函数的内容,而不是wrapped()
的。改进
如您所见,这两个装饰器中有很多重复的代码。 鉴于这种相似性,您最好创建一个实际上是装饰器工厂的通用工厂,换句话说,是一个创建其他装饰器的装饰器函数。 这样就会减少代码重复,并允许遵循 DRY 原则。
为了使代码更具可读性,您可以为工厂生成的装饰器指定一个更具描述性的名称:
或者甚至像这样组合它们:
效率
虽然上面的示例完成了所有工作,但生成的代码涉及相当多的开销,其形式为一次应用多个装饰器时的无关函数调用。 这可能并不重要,具体取决于具体的用法(例如,可能是 I/O 限制)。
如果装饰函数的速度很重要,则可以通过编写稍微不同的装饰器工厂函数来将开销保持在单个额外函数调用上,该函数实现一次添加所有标签,因此它可以生成避免产生额外函数调用的代码通过为每个标签使用单独的装饰器。
这需要在装饰器本身中添加更多代码,但这仅在将其应用于函数定义时运行,而不是在稍后调用它们本身时运行。 如前所述,这也适用于使用 lambda 函数创建更具可读性的名称。 样本:
You could make two separate decorators that do what you want as illustrated directly below. Note the use of
*args, **kwargs
in the declaration of thewrapped()
function which supports the decorated function having multiple arguments (which isn't really necessary for the examplesay()
function, but is included for generality).For similar reasons, the
functools.wraps
decorator is used to change the meta attributes of the wrapped function to be those of the one being decorated. This makes error messages and embedded function documentation (func.__doc__
) be those of the decorated function instead ofwrapped()
's.Refinements
As you can see there's a lot of duplicate code in these two decorators. Given this similarity it would be better for you to instead make a generic one that was actually a decorator factory—in other words, a decorator function that makes other decorators. That way there would be less code repetition—and allow the DRY principle to be followed.
To make the code more readable, you can assign a more descriptive name to the factory-generated decorators:
or even combine them like this:
Efficiency
While the above examples do all work, the code generated involves a fair amount of overhead in the form of extraneous function calls when multiple decorators are applied at once. This may not matter, depending the exact usage (which might be I/O-bound, for instance).
If speed of the decorated function is important, the overhead can be kept to a single extra function call by writing a slightly different decorator factory-function which implements adding all the tags at once, so it can generate code that avoids the addtional function calls incurred by using separate decorators for each tag.
This requires more code in the decorator itself, but this only runs when it's being applied to function definitions, not later when they themselves are called. This also applies when creating more readable names by using
lambda
functions as previously illustrated. Sample:做同样事情的另一种方法:
或者更灵活:
Another way of doing the same thing:
Or, more flexibly:
当调用时,您需要以下函数:
回来:
简单的解决方案
为了最简单地做到这一点,让装饰器返回关闭函数(闭包)的 lambda(匿名函数)并调用它:
现在根据需要使用它们:
现在:
简单解决方案的问题
但我们似乎几乎迷失了原来的函数。
为了找到它,我们需要深入研究每个 lambda 的闭包,其中一个闭包隐藏在另一个闭包中:
因此,如果我们对此函数添加文档,或者希望能够装饰采用多个参数的函数,或者我们只是想知道我们在调试会话中正在查看什么函数,我们需要对包装器做更多的事情。
全功能解决方案 - 克服大部分问题
我们在标准库的
functools
模块中拥有装饰器wraps
!不幸的是,仍然有一些样板,但这已经是我们能做到的最简单的了。
在 Python 3 中,您还可以默认分配
__qualname__
和__annotations__
。所以现在:
现在:
结论
所以我们看到
wraps
使包装函数几乎可以完成所有操作,除了告诉我们该函数将什么作为参数。还有其他模块可能会尝试解决该问题,但标准库中尚未提供解决方案。
You want the following function, when called:
To return:
Simple solution
To most simply do this, make decorators that return lambdas (anonymous functions) that close over the function (closures) and call it:
Now use them as desired:
and now:
Problems with the simple solution
But we seem to have nearly lost the original function.
To find it, we'd need to dig into the closure of each lambda, one of which is buried in the other:
So if we put documentation on this function, or wanted to be able to decorate functions that take more than one argument, or we just wanted to know what function we were looking at in a debugging session, we need to do a bit more with our wrapper.
Full featured solution - overcoming most of these problems
We have the decorator
wraps
from thefunctools
module in the standard library!It is unfortunate that there's still some boilerplate, but this is about as simple as we can make it.
In Python 3, you also get
__qualname__
and__annotations__
assigned by default.So now:
And now:
Conclusion
So we see that
wraps
makes the wrapping function do almost everything except tell us exactly what the function takes as arguments.There are other modules that may attempt to tackle the problem, but the solution is not yet in the standard library.
装饰器接受函数定义并创建一个新函数来执行该函数并转换结果。
相当于:
示例:
这
相当于
65 <=> 'a'
要理解装饰器,需要注意的是,装饰器创建了一个新函数 do ,它在内部执行函数并转换结果。
A decorator takes the function definition and creates a new function that executes this function and transforms the result.
is equivalent to:
Example:
This
is equivalent to this
65 <=> 'a'
To understand the decorator, it is important to notice, that decorator created a new function do which is inner that executes function and transforms the result.
这个答案早已得到解答,但我想我会分享我的 Decorator 类,这使得编写新的装饰器变得简单而紧凑。
一方面,我认为这使得装饰器的行为非常清晰,而且也使得很容易非常简洁地定义新的装饰器。 示例,您可以将其解决为:
使用它来执行更复杂的任务,例如装饰器,它自动使函数递归地应用于迭代器中的所有参数:
对于上面列出的
您还可以 示例在装饰器的实例化中不包含
list
类型,因此在最终的打印语句中,该方法应用于列表本身,而不是列表的元素。This answer has long been answered, but I thought I would share my Decorator class which makes writing new decorators easy and compact.
For one I think this makes the behavior of decorators very clear, but it also makes it easy to define new decorators very concisely. For the example listed above, you could then solve it as:
You could also use it to do more complex tasks, like for instance a decorator which automatically makes the function get applied recursively to all arguments in an iterator:
Which prints:
Notice that this example didn't include the
list
type in the instantiation of the decorator, so in the final print statement the method gets applied to the list itself, not the elements of the list.也可以在Class中写装饰器
You can also write decorator in Class
这是链接装饰器的一个简单示例。 请注意最后一行 - 它显示了幕后发生的事情。
输出看起来像:
Here is a simple example of chaining decorators. Note the last line - it shows what is going on under the covers.
The output looks like:
Paolo Bergantino 的答案 具有仅使用 stdlib 的巨大优势,并且适用于这个没有 的简单示例装饰器参数或装饰函数参数。
然而,如果您想处理更一般的情况,它有 3 个主要限制:
makestyle(style='bold')
装饰器并不简单。类型错误
。@functools.wraps
创建的包装器中,很难根据名称访问参数。 事实上,参数可以出现在 *args 中,也可以出现在**kwargs
中,或者根本不出现(如果它是可选的)。我写了
decopatch
来解决第一个问题,并写了makefun.wraps
解决其他问题二。 请注意,makefun
利用了与著名的decorator
库。这就是您如何创建带有参数的装饰器,返回真正保留签名的包装器:
decopatch 为您提供了另外两种开发风格,可以根据您的喜好隐藏或显示各种 python 概念。 最紧凑的样式如下:
在这两种情况下,您都可以检查装饰器是否按预期工作:
请参阅 文档了解详细信息。
Paolo Bergantino's answer has the great advantage of only using the stdlib, and works for this simple example where there are no decorator arguments nor decorated function arguments.
However it has 3 major limitations if you want to tackle more general cases:
makestyle(style='bold')
decorator is non-trivial.@functools.wraps
do not preserve the signature, so if bad arguments are provided they will start executing, and might raise a different kind of error than the usualTypeError
.@functools.wraps
to access an argument based on its name. Indeed the argument can appear in*args
, in**kwargs
, or may not appear at all (if it is optional).I wrote
decopatch
to solve the first issue, and wrotemakefun.wraps
to solve the other two. Note thatmakefun
leverages the same trick than the famousdecorator
lib.This is how you would create a decorator with arguments, returning truly signature-preserving wrappers:
decopatch
provides you with two other development styles that hide or show the various python concepts, depending on your preferences. The most compact style is the following:In both cases you can check that the decorator works as expected:
Please refer to the documentation for details.
说到计数器示例 - 如上所述,计数器将在使用装饰器的所有函数之间共享:
这样,您的装饰器可以重用于不同的函数(或用于多次装饰同一函数:func_counter1 = counter(func); func_counter2 = counter(func)),并且 counter 变量将保持私有。
Speaking of the counter example - as given above, the counter will be shared between all functions that use the decorator:
That way, your decorator can be reused for different functions (or used to decorate the same function multiple times:
func_counter1 = counter(func); func_counter2 = counter(func)
), and the counter variable will remain private to each.用不同数量的参数装饰函数:
结果:
Decorate functions with different number of arguments:
Result:
考虑下面的装饰器,请注意,我们将wrapper()函数作为对象返回,
因此其
计算结果为this
。请注意,x不是say(),而是内部调用say()的包装器对象。 这就是装饰器的工作原理。 它总是返回调用实际函数的包装对象。
在链接的情况下,这
会转换为
下面是完整的代码
上面的代码将返回
希望这有帮助
Consider the following decorator, note that we are returning the wrapper() function as an object
So This
evaluates to this
Note that x is not the say() but the wrapper object that calls say() internally. That is how decorator works. It always returns the wrapper object which calls the actual function.
In case of chaining this
gets converted to this
Below is the complete code
The above code will return
Hope this helps
使用下面的
make_bold()
和make_italic()
:您可以将它们用作
say()
的装饰器,如下所示:输出:
当然,您可以直接使用
make_bold()
和make_italic()
而不使用装饰器,如下所示:简而言之:
输出:
With
make_bold()
andmake_italic()
below:You can use them as decorators with
say()
as shown below:Output:
And of course, you can directly use
make_bold()
andmake_italic()
without decorators as shown below:In short:
Output:
当您需要在装饰器中添加自定义参数,将其传递给最终函数然后使用它时,我添加了一个案例。
装饰器:
以及最终的功能:
I add a case when you need to add custom parameters in decorator, pass it to final function and then work it with.
the very decorators:
and the final function:
用于绘制图像的嵌套装饰器的另一个示例:
现在,让我们首先使用嵌套装饰器显示没有轴标签的彩色图像:
接下来,让我们使用嵌套装饰器显示没有轴标签的灰度图像
remove_axis
和plot_gray
(我们需要cmap='gray'
,否则默认颜色图是viridis
,所以灰度图像默认不以黑白色调显示,除非明确指定)上述函数调用简化为以下嵌套调用
Yet another example of nested decorators for plotting an image:
Now, let's show a color image first without axis labels using the nested decorators:
Next, let's show a gray scale image without axis labels using the nested decorators
remove_axis
andplot_gray
(we need tocmap='gray'
, otherwise the default colormap isviridis
, so a grayscale image is by default not displayed in black and white shades, unless explicitly specified)The above function call reduces down to the following nested call
python 3.9 中的 lambda 表达式可以用作装饰器。
对于您的问题
如果您想在第一次函数调用后重用上述 lambda,可以将它们分配给变量并重用。 下面的例子。
python 3.9 the lambda expressions can be used as decorators.
For your question
If you want to reuse the above lambdas after first function call, it is possible to assign them to a variable and reuse. Example below.
问题
解决方案
make_bold
make_italic
我向
make_italic
添加了一行以修改包装函数返回的值。这行代码对某些人来说可能有用,也可能没用:
Problem
Solution
make_bold
make_italic
I added one additional line to
make_italic
to modify the value returned by the wrapped function.The line of code may or may not be useful to some people: