Python装饰器入门

In this tutorial on decorators, we’ll look at what they are and how to create and use them. Decorators provide a simple syntax for calling higher-order functions.

在有关装饰器的本教程中,我们将了解它们是什么以及如何创建和使用它们。 装饰器提供了用于调用高阶函数的简单语法。

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

根据定义,装饰器是一个函数,它接受另一个函数并扩展后一个函数的行为,而无需显式修改它。

This sounds confusing, but it’s really not, especially after you’ve seen a few examples of how decorators work. You can find all the examples from this article here.

这听起来令人困惑,但实际上并非如此,尤其是在您看到一些装饰器如何工作的示例之后。 您可以在此处找到本文中的所有示例。

Free Bonus: Click here to get access to a free “The Power of Python Decorators” guide that shows you 3 advanced decorator patterns and techniques you can use to write to cleaner and more Pythonic programs.

免费红利: 单击此处可获得免费的“ Python装饰器的威力”指南 ,该指南向您展示了3种高级装饰器模式和技术,可用于编写更清洁和更多Pythonic程序。

Decorators Cheat Sheet: Click here to get access to a free 3-page Python decorators cheat sheet that summarizes the techniques explained in this tutorial.

Decorators备忘单: 单击此处可访问免费的3页Python装饰器备忘单 ,其中总结了本教程中介绍的技术。

Updates:

更新:

  • 08/22/2018: Major update adding more examples and more advanced decorators
  • 01/12/2016: Updated examples to Python 3 (v3.5.1) syntax and added a new example
  • 11/01/2015: Added a brief explanation on the functools.wraps() decorator
  • 2018年8月22日:重大更新添加了更多示例和更多高级装饰器
  • 2016年1月12日:将示例更新为Python 3(v3.5.1)语法,并添加了新示例
  • 2015年11月1日:添加了关于functools.wraps()装饰器的简要说明

功能 (Functions)

Before you can understand decorators, you must first understand how functions work. For our purposes, a function returns a value based on the given arguments. Here is a very simple example:

在了解装饰器之前,您必须首先了解函数的工作方式。 就我们的目的而言, 函数基于给定的arguments返回一个值 。 这是一个非常简单的示例:

 >>> >>>  def def add_oneadd_one (( numbernumber ):
):
...     ...     return return number number + + 1

1

>>> >>>  add_oneadd_one (( 22 )
)
3
3

In general, functions in Python may also have side effects rather than just turning an input into an output. The print() function is a basic example of this: it returns None while having the side effect of outputting something to the console. However, to understand decorators, it is enough to think about functions as something that turns given arguments into a value.

通常,Python中的函数可能还会产生副作用,而不仅仅是将输入转换为输出。 print()函数是一个基本的示例:它返回None同时会产生向控制台输出内容的副作用。 但是,要了解装饰器,将函数视为将给定参数转换为值的东西就足够了。

Note: In functional programming, you work (almost) only with pure functions without side effects. While not a purely functional language, Python supports many of the functional programming concepts, including functions as first-class objects.

注意:函数式编程中 ,(几乎)仅使用纯函数而没有副作用。 虽然不是纯粹的函数式语言,但是Python支持许多函数式编程概念,包括作为一流对象的函数。

一流的对象 (First-Class Objects)

In Python, functions are first-class objects. This means that functions can be passed around and used as arguments, just like any other object (string, int, float, list, and so on). Consider the following three functions:

在Python中,函数是一类对象 。 这意味着函数可以像其他任何对象(字符串,整数,浮点数,列表等)一样传递并用作参数 。 请考虑以下三个功能:

Here, say_hello() and be_awesome() are regular functions that expect a name given as a string. The greet_bob() function however, expects a function as its argument. We can, for instance, pass it the say_hello() or the be_awesome() function:

在这里, say_hello()be_awesome()是常规函数,它们期望以字符串形式给出名称。 但是, greet_bob()函数需要一个函数作为其参数。 例如,我们可以将say_hello()be_awesome()函数传递给它:

 >>> >>>  greet_bobgreet_bob (( say_hellosay_hello )
)
'Hello Bob'

'Hello Bob'

>>> >>>  greet_bobgreet_bob (( be_awesomebe_awesome )
)
'Yo Bob, together we are the awesomest!'
'Yo Bob, together we are the awesomest!'

Note that greet_bob(say_hello) refers to two functions, but in different ways: greet_bob() and say_hello. The say_hello function is named without parentheses. This means that only a reference to the function is passed. The function is not executed. The greet_bob() function, on the other hand, is written with parentheses, so it will be called as usual.

请注意, greet_bob(say_hello)引用了两个函数,但使用的方式不同: greet_bob()say_hellosay_hello函数的名称不带括号。 这意味着仅传递对该函数的引用。 该功能未执行。 另一方面, greet_bob()函数是用括号编写的,因此将照常调用它。

内部功能 (Inner Functions)

It’s possible to define functions inside other functions. Such functions are called inner functions. Here’s an example of a function with two inner functions:

可以在其他函数中定义函数 。 这些功能称为内部功能 。 这是带有两个内部函数的函数示例:

What happens when you call the parent() function? Think about this for a minute. The output will be as follows:

调用parent()函数时会发生什么? 考虑一下。 输出将如下所示:

 >>> >>>  parentparent ()
()
Printing from the parent() function
Printing from the parent() function
Printing from the second_child() function
Printing from the second_child() function
Printing from the first_child() function
Printing from the first_child() function

Note that the order in which the inner functions are defined does not matter. Like with any other functions, the printing only happens when the inner functions are executed.

注意,定义内部功能的顺序无关紧要。 与任何其他功能一样,仅在执行内部功能时才进行打印。

Furthermore, the inner functions are not defined until the parent function is called. They are locally scoped to parent(): they only exist inside the parent() function as local variables. Try calling first_child(). You should get an error:

此外,在调用父函数之前,不定义内部函数。 它们在本地范围内为parent() :它们仅在parent()函数内部作为局部变量存在。 尝试调用first_child() 。 您应该得到一个错误:

Whenever you call parent(), the inner functions first_child() and second_child() are also called. But because of their local scope, they aren’t available outside of the parent() function.

每当您调用parent() ,内部函数first_child()second_child()也会被调用。 但是由于它们的本地作用域,它们在parent()函数之外不可用。

从函数返回函数 (Returning Functions From Functions)

Python also allows you to use functions as return values. The following example returns one of the inner functions from the outer parent() function:

Python还允许您将函数用作返回值。 以下示例从外部parent()函数返回内部函数之一:

 def def parentparent (( numnum ):
    ):
    def def first_childfirst_child ():
        ():
        return return "Hi, I am Emma"

    "Hi, I am Emma"

    def def second_childsecond_child ():
        ():
        return return "Call me Liam"

    "Call me Liam"

    if if num num == == 11 :
        :
        return return first_child
    first_child
    elseelse :
        :
        return return second_child
second_child

Note that you are returning first_child without the parentheses. Recall that this means that you are returning a reference to the function first_child. In contrast first_child() with parentheses refers to the result of evaluating the function. This can be seen in the following example:

请注意,您将返回不带括号的first_child 。 回想一下,这意味着您将返回对函数first_child的引用 。 相反,带括号的first_child()对函数求值的结果。 在以下示例中可以看到:

The somewhat cryptic output simply means that the first variable refers to the local first_child() function inside of parent(), while second points to second_child().

有点晦涩难懂的输出只是意味着第first变量引用了parent()内部的本地first_child()函数,而second指向了second_child()

You can now use first and second as if they are regular functions, even though the functions they point to can’t be accessed directly:

现在,您可以像使用常规函数一样使用firstsecond ,即使它们指向的函数不能直接访问:

 >>> >>>  firstfirst ()
()
'Hi, I am Emma'

'Hi, I am Emma'

>>> >>>  secondsecond ()
()
'Call me Liam'
'Call me Liam'

Finally, note that in the earlier example you executed the inner functions within the parent function, for instance first_child(). However, in this last example, you did not add parentheses to the inner functions—first_child—upon returning. That way, you got a reference to each function that you could call in the future. Make sense?

最后,请注意,在前面的示例中,您在父函数中执行了内部函数,例如first_child() 。 但是,在最后一个示例中,返回时并未在内部函数first_child添加括号。 这样,您就可以引用将来可以调用的每个函数。 合理?

简单的装饰器 (Simple Decorators)

Now that you’ve seen that functions are just like any other object in Python, you’re ready to move on and see the magical beast that is the Python decorator. Let’s start with an example:

既然您已经看到函数就像Python中的任何其他对象一样,您就可以继续前进,并看到Python装饰器这一神奇的野兽。 让我们从一个例子开始:

Can you guess what happens when you call say_whee()? Try it:

您能猜出在调用say_whee()时会发生什么吗? 试试吧:

 >>> >>>  say_wheesay_whee ()
()
Something is happening before the function is called.
Something is happening before the function is called.
Whee!
Whee!
Something is happening after the function is called.
Something is happening after the function is called.

To understand what’s going on here, look back at the previous examples. We are literally just applying everything you have learned so far.

要了解这里发生的情况,请回顾前面的示例。 从字面上看,我们只是在应用您到目前为止所学的所有知识。

The so-called decoration happens at the following line:

所谓的修饰发生在以下行:

In effect, the name say_whee now points to the wrapper() inner function. Remember that you return wrapper as a function when you call my_decorator(say_whee):

实际上,名称say_whee现在指向wrapper()内部函数。 请记住,在调用my_decorator(say_whee)时,您将wrapper作为函数返回:

 >>> >>>  say_whee
say_whee
<function my_decorator.<locals>.wrapper at 0x7f3c5dfd42f0>
<function my_decorator.<locals>.wrapper at 0x7f3c5dfd42f0>

However, wrapper() has a reference to the original say_whee() as func, and calls that function between the two calls to print().

但是, wrapper()将原始的say_whee()引用作为func ,并在两次调用print()之间调用该函数。

Put simply: decorators wrap a function, modifying its behavior.

简而言之: 装饰器包装一个函数,修改其行为。

Before moving on, let’s have a look at a second example. Because wrapper() is a regular Python function, the way a decorator modifies a function can change dynamically. So as not to disturb your neighbors, the following example will only run the decorated code during the day:

在继续之前,让我们看第二个例子。 因为wrapper()是常规的Python函数,所以装饰器修改函数的方式可以动态更改。 为了不打扰您的邻居,以下示例仅在白天运行经过修饰的代码:

If you try to call say_whee() after bedtime, nothing will happen:

如果您尝试在睡前调用say_whee() ,则不会发生任何事情:

 >>> >>>  say_wheesay_whee ()
()
>>>
>>>

语法糖! (Syntactic Sugar!)

The way you decorated say_whee() above is a little clunky. First of all, you end up typing the name say_whee three times. In addition, the decoration gets a bit hidden away below the definition of the function.

您装饰上面的say_whee()的方式有些笨拙。 首先,您最终会键入三次say_whee名称。 另外,在函数的定义之下,装饰也隐藏了一些。

Instead, Python allows you to use decorators in a simpler way with the @ symbol, sometimes called the “pie” syntax. The following example does the exact same thing as the first decorator example:

相反,Python允许您通过@符号 (有时称为“ pie”语法) 以更简单的方式使用装饰器 。 下面的示例与第一个装饰器示例完全相同:

So, @my_decorator is just an easier way of saying say_whee = my_decorator(say_whee). It’s how you apply a decorator to a function.

因此, @my_decorator只是说say_whee = my_decorator(say_whee)一种简单方法。 这就是将装饰器应用于函数的方式。

重用装饰器 (Reusing Decorators)

Recall that a decorator is just a regular Python function. All the usual tools for easy reusability are available. Let’s move the decorator to its own module that can be used in many other functions.

回想一下,装饰器只是一个常规的Python函数。 提供了所有易于重用的常用工具。 让我们将装饰器移动到可以在许多其他功能中使用的模块

Create a file called decorators.py with the following content:

创建一个名为decorators.py的文件,其内容如下:

 def def do_twicedo_twice (( funcfunc ):
    ):
    def def wrapper_do_twicewrapper_do_twice ():
        ():
        funcfunc ()
        ()
        funcfunc ()
    ()
    return return wrapper_do_twice
wrapper_do_twice

Note: You can name your inner function whatever you want, and a generic name like wrapper() is usually okay. You’ll see a lot of decorators in this article. To keep them apart, we’ll name the inner function with the same name as the decorator but with a wrapper_ prefix.

注意:您可以随意命名内部函数,通常可以使用诸如wrapper()类的通用名称。 您将在本文中看到很多装饰器。 为了使它们分开,我们将内部函数命名为与装饰器相同的名称,但使用wrapper_前缀。

You can now use this new decorator in other files by doing a regular import:

现在,您可以通过常规导入在其他文件中使用此新装饰器:

When you run this example, you should see that the original say_whee() is executed twice:

运行此示例时,应该看到原始的say_whee()被执行了两次:

 >>> >>>  say_wheesay_whee ()
()
Whee!
Whee!
Whee!
Whee!

Free Bonus: Click here to get access to a free the “The Power of Python Decorators” guide that shows you 3 advanced decorator patterns and techniques you can use to write to cleaner and more Pythonic programs.

免费红利: 单击此处可获得免费的“ Python装饰器的力量”指南 ,该指南向您展示了3种高级装饰器模式和技术,可用于编写更干净和更多的Pythonic程序。

用参数装饰函数 (Decorating Functions With Arguments)

Say that you have a function that accepts some arguments. Can you still decorate it? Let’s try:

假设您有一个接受某些参数的函数。 你还能装饰吗? 我们试试吧:

Unfortunately, running this code raises an error:

不幸的是,运行以下代码会引发错误:

 >>> >>>  greetgreet (( "World""World" )
)
Traceback (most recent call last):
  File Traceback (most recent call last):
  File "<stdin>", line "<stdin>" , line 1, in 1 , in <module>
<module>
TypeError: TypeError : wrapper_do_twice() takes 0 positional arguments but 1 was given
wrapper_do_twice() takes 0 positional arguments but 1 was given

The problem is that the inner function wrapper_do_twice() does not take any arguments, but name="World" was passed to it. You could fix this by letting wrapper_do_twice() accept one argument, but then it would not work for the say_whee() function you created earlier.

问题在于内部函数wrapper_do_twice()不接受任何参数,但wrapper_do_twice() name="World"传递给它。 您可以通过允许wrapper_do_twice()接受一个参数来解决此问题,但是它不适用于您先前创建的say_whee()函数。

The solution is to use *args and **kwargs in the inner wrapper function. Then it will accept an arbitrary number of positional and keyword arguments. Rewrite decorators.py as follows:

解决方案是在内部包装函数中使用*args**kwargs 。 然后它将接受任意数量的位置和关键字参数。 重写decorators.py ,如下所示:

The wrapper_do_twice() inner function now accepts any number of arguments and passes them on to the function it decorates. Now both your say_whee() and greet() examples works:

现在, wrapper_do_twice()内部函数可以接受任意数量的参数,并将它们传递给它装饰的函数。 现在您的say_whee()greet()示例都可以使用:

 >>> >>>  say_wheesay_whee ()
()
Whee!
Whee!
Whee!

Whee!

>>> >>>  greetgreet (( "World""World" )
)
Hello World
Hello World
Hello World
Hello World

从装饰函数返回值 (Returning Values From Decorated Functions)

What happens to the return value of decorated functions? Well, that’s up to the decorator to decide. Let’s say you decorate a simple function as follows:

装饰函数的返回值会怎样? 好吧,这取决于装饰者来决定。 假设您装饰了一个简单的函数,如下所示:

Try to use it:

尝试使用它:

 >>> >>>  hi_adam hi_adam = = return_greetingreturn_greeting (( "Adam""Adam" )
)
Creating greeting
Creating greeting
Creating greeting
Creating greeting
>>> >>>  printprint (( hi_adamhi_adam )
)
None
None

Oops, your decorator ate the return value from the function.

糟糕,您的装饰器会吃掉函数的返回值。

Because the do_twice_wrapper() doesn’t explicitly return a value, the call return_greeting("Adam") ended up returning None.

由于do_twice_wrapper()没有明确返回值,因此调用return_greeting("Adam")最终返回None

To fix this, you need to make sure the wrapper function returns the return value of the decorated function. Change your decorators.py file:

要解决此问题,您需要确保包装函数返回装饰函数的返回值 。 更改您的decorators.py文件:

The return value from the last execution of the function is returned:

返回上一次执行该函数的返回值:

 >>> >>>  return_greetingreturn_greeting (( "Adam""Adam" )
)
Creating greeting
Creating greeting
Creating greeting
Creating greeting
'Hi Adam'
'Hi Adam'

你到底是谁? (Who Are You, Really?)

A great convenience when working with Python, especially in the interactive shell, is its powerful introspection ability. Introspection is the ability of an object to know about its own attributes at runtime. For instance, a function knows its own name and documentation:

使用Python时(特别是在交互式shell中)最大的便利是其强大的自省功能。 自省是对象在运行时知道其自身属性的能力。 例如,一个函数知道自己的名称和说明文件:

The introspection works for functions you define yourself as well:

内省适用于您自己定义的函数:

 >>> >>>  say_whee
say_whee
<function do_twice.<locals>.wrapper_do_twice at 0x7f43700e52f0>

<function do_twice.<locals>.wrapper_do_twice at 0x7f43700e52f0>

>>> >>>  say_wheesay_whee .. __name__
__name__
'wrapper_do_twice'

'wrapper_do_twice'

>>> >>>  helphelp (( say_wheesay_whee )
)
Help on function wrapper_do_twice in module decorators:

Help on function wrapper_do_twice in module decorators:

wrapper_do_twice()
wrapper_do_twice()

However, after being decorated, say_whee() has gotten very confused about its identity. It now reports being the wrapper_do_twice() inner function inside the do_twice() decorator. Although technically true, this is not very useful information.

但是,在经过修饰后, say_whee()对它的身份感到非常困惑。 现在报告它是do_twice()装饰器中的wrapper_do_twice()内部函数。 尽管从技术上讲是正确的,但这不是非常有用的信息。

To fix this, decorators should use the @functools.wraps decorator, which will preserve information about the original function. Update decorators.py again:

要解决此问题,装饰器应使用@functools.wraps装饰器,该装饰器将保留有关原始功能的信息。 再次更新decorators.py

You do not need to change anything about the decorated say_whee() function:

您不需要更改任何有关修饰的say_whee()函数的内容:

 >>> >>>  say_whee
say_whee
<function say_whee at 0x7ff79a60f2f0>

<function say_whee at 0x7ff79a60f2f0>

>>> >>>  say_wheesay_whee .. __name__
__name__
'say_whee'

'say_whee'

>>> >>>  helphelp (( say_wheesay_whee )
)
Help on function say_whee in module whee:

Help on function say_whee in module whee:

say_whee()
say_whee()

Much better! Now say_whee() is still itself after decoration.

好多了! 现在say_whee()仍然是经过修饰的本身。

Technical Detail: The @functools.wraps decorator uses the function functools.update_wrapper() to update special attributes like __name__ and __doc__ that are used in the introspection.

技术细节: @functools.wraps装饰器使用 functools.update_wrapper()函数来更新自检中使用的特殊属性,例如__name____doc__

现实世界中的一些例子 (A Few Real World Examples)

Let’s look at a few more useful examples of decorators. You’ll notice that they’ll mainly follow the same pattern that you’ve learned so far:

让我们看一些装饰器的有用示例。 您会注意到,它们将主要遵循您到目前为止所学的相同模式:

This formula is a good boilerplate template for building more complex decorators.

此公式是用于构建更复杂的装饰器的良好样板模板。

Note: In later examples, we will assume that these decorators are saved in your decorators.py file as well. Recall that you can download all the examples in this tutorial.

注意:在后面的示例中,我们将假定这些装饰器也保存在您的decorators.py文件中。 回想一下,您可以下载本教程中的所有示例

计时功能 (Timing Functions)

Let’s start by creating a @timer decorator. It will measure the time a function takes to execute and print the duration to the console. Here’s the code:

让我们从创建一个@timer装饰器开始。 它将测量功能执行所需的时间并将持续时间打印到控制台。 这是代码:

 import import functools
functools
import import time

time

def def timertimer (( funcfunc ):
    ):
    """Print the runtime of the decorated function"""
    """Print the runtime of the decorated function"""
    @functools@functools .. wrapswraps (( funcfunc )
    )
    def def wrapper_timerwrapper_timer (( ** argsargs , , **** kwargskwargs ):
        ):
        start_time start_time = = timetime .. perf_counterperf_counter ()    ()    # 1
        # 1
        value value = = funcfunc (( ** argsargs , , **** kwargskwargs )
        )
        end_time end_time = = timetime .. perf_counterperf_counter ()      ()      # 2
        # 2
        run_time run_time = = end_time end_time - - start_time    start_time    # 3
        # 3
        printprint (( ff "Finished "Finished  {func.__name__!r}{func.__name__!r}  in  in  {run_time:.4f}{run_time:.4f}  secs" secs" )
        )
        return return value
    value
    return return wrapper_timer

wrapper_timer

@timer
@timer
def def waste_some_timewaste_some_time (( num_timesnum_times ):
    ):
    for for _ _ in in rangerange (( num_timesnum_times ):
        ):
        sumsum ([([ ii **** 2 2 for for i i in in rangerange (( 1000010000 )])
)])

This decorator works by storing the time just before the function starts running (at the line marked # 1) and just after the function finishes (at # 2). The time the function takes is then the difference between the two (at # 3). We use the time.perf_counter() function, which does a good job of measuring time intervals. Here are some examples of timings:

该装饰器的工作原理是存储函数开始运行之前(在标记为# 1的行)和函数完成之后(在# 2 )的时间。 函数花费的时间就是两者之间的差(在# 3 )。 我们使用time.perf_counter()函数,该函数可以很好地测量时间间隔。 以下是一些计时示例:

Run it yourself. Work through the code line by line. Make sure you understand how it works. Don’t worry if you don’t get it, though. Decorators are advanced beings. Try to sleep on it or make a drawing of the program flow.

自己运行。 逐行处理代码。 确保您了解其工作原理。 不过,不要担心,如果您不了解它。 装饰者是高级生物。 尝试在上面睡觉或绘制程序流程。

Note: The @timer decorator is great if you just want to get an idea about the runtime of your functions. If you want to do more precise measurements of code, you should instead consider the timeit module in the standard library. It temporarily disables garbage collection and runs multiple trials to strip out noise from quick function calls.

注意:如果只想了解函数的运行时, @timer装饰器非常有用。 如果要对代码进行更精确的测量,则应考虑使用标准库中的timeit模块 。 它暂时禁用垃圾收集,并运行多次试验以消除快速函数调用中的噪音。

调试代码 (Debugging Code)

The following @debug decorator will print the arguments a function is called with as well as its return value every time the function is called:

以下@debug装饰器将在每次调用该函数时显示调用该函数的参数及其返回值:

 import import functools

functools

def def debugdebug (( funcfunc ):
    ):
    """Print the function signature and return value"""
    """Print the function signature and return value"""
    @functools@functools .. wrapswraps (( funcfunc )
    )
    def def wrapper_debugwrapper_debug (( ** argsargs , , **** kwargskwargs ):
        ):
        args_repr args_repr = = [[ reprrepr (( aa ) ) for for a a in in argsargs ]                      ]                      # 1
        # 1
        kwargs_repr kwargs_repr = = [[ ff "" {k}{k} == {v!r}{v!r} " " for for kk , , v v in in kwargskwargs .. itemsitems ()]  ()]  # 2
        # 2
        signature signature = = ", "", " .. joinjoin (( args_repr args_repr + + kwargs_reprkwargs_repr )           )           # 3
        # 3
        printprint (( ff "Calling "Calling  {func.__name__}{func.__name__} (( {signature}{signature} )")" )
        )
        value value = = funcfunc (( ** argsargs , , **** kwargskwargs )
        )
        printprint (( ff "" {func.__name__!r}{func.__name__!r}  returned  returned  {value!r}{value!r} "" )           )           # 4
        # 4
        return return value
    value
    return return wrapper_debug
wrapper_debug

The signature is created by joining the string representations of all the arguments. The numbers in the following list correspond to the numbered comments in the code:

通过连接所有参数的字符串表示来创建签名。 下表中的数字与代码中带注释的数字相对应:

  1. Create a list of the positional arguments. Use repr() to get a nice string representing each argument.
  2. Create a list of the keyword arguments. The f-string formats each argument as key=value where the !r specifier means that repr() is used to represent the value.
  3. The lists of positional and keyword arguments is joined together to one signature string with each argument separated by a comma.
  4. The return value is printed after the function is executed.
  1. 创建位置参数列表。 使用repr()获得代表每个参数的漂亮字符串。
  2. 创建关键字参数列表。 f字符串将每个参数格式化为key=value ,其中!r表示符表示repr()用于表示该值。
  3. 位置参数和关键字参数的列表连接在一起形成一个签名字符串,每个参数用逗号分隔。
  4. 执行该功能后,将打印返回值。

Let’s see how the decorator works in practice by applying it to a simple function with one position and one keyword argument:

让我们看看装饰器在实践中是如何工作的,方法是将其应用于具有一个位置和一个关键字参数的简单函数:

Note how the @debug decorator prints the signature and return value of the make_greeting() function:

请注意@debug装饰器如何打印make_greeting()函数的签名和返回值:

 >>> >>>  make_greetingmake_greeting (( "Benjamin""Benjamin" )
)
Calling make_greeting('Benjamin')
Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'
'make_greeting' returned 'Howdy Benjamin!'
'Howdy Benjamin!'

'Howdy Benjamin!'

>>> >>>  make_greetingmake_greeting (( "Richard""Richard" , , ageage == 112112 )
)
Calling make_greeting('Richard', age=112)
Calling make_greeting('Richard', age=112)
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'
'Whoa Richard! 112 already, you are growing up!'

'Whoa Richard! 112 already, you are growing up!'

>>> >>>  make_greetingmake_greeting (( namename == "Dorrisile""Dorrisile" , , ageage == 116116 )
)
Calling make_greeting(name='Dorrisile', age=116)
Calling make_greeting(name='Dorrisile', age=116)
'make_greeting' returned 'Whoa Dorrisile! 116 already, you are growing up!'
'make_greeting' returned 'Whoa Dorrisile! 116 already, you are growing up!'
'Whoa Dorrisile! 116 already, you are growing up!'
'Whoa Dorrisile! 116 already, you are growing up!'

This example might not seem immediately useful since the @debug decorator just repeats what you just wrote. It’s more powerful when applied to small convenience functions that you don’t call directly yourself.

由于@debug装饰器仅重复您刚才编写的内容,因此该示例似乎并不立即有用。 当将其应用于不直接调用的小型便捷功能时,它会更强大。

The following example calculates an approximation to the mathematical constant e:

下面的示例计算数学常数e的近似值:

This example also shows how you can apply a decorator to a function that has already been defined. The approximation of e is based on the following series expansion:

此示例还显示了如何将装饰器应用于已定义的函数。 e的近似值基于以下级数展开

Series for calculating mathematical constant e

When calling the approximate_e() function, you can see the @debug decorator at work:

当调用approximate_e()函数时,您可以看到@debug装饰器在起作用:

 >>> >>>  approximate_eapproximate_e (( 55 )
)
Calling factorial(0)
Calling factorial(0)
'factorial' returned 1
'factorial' returned 1
Calling factorial(1)
Calling factorial(1)
'factorial' returned 1
'factorial' returned 1
Calling factorial(2)
Calling factorial(2)
'factorial' returned 2
'factorial' returned 2
Calling factorial(3)
Calling factorial(3)
'factorial' returned 6
'factorial' returned 6
Calling factorial(4)
Calling factorial(4)
'factorial' returned 24
'factorial' returned 24
2.708333333333333
2.708333333333333

In this example, you get a decent approximation to the true value e = 2.718281828, adding only 5 terms.

在此示例中,您仅对5个项进行了近似计算即可得出真实值e = 2.718281828。

减速代码 (Slowing Down Code)

This next example might not seem very useful. Why would you want to slow down your Python code? Probably the most common use case is that you want to rate-limit a function that continuously checks whether a resource—like a web page—has changed. The @slow_down decorator will sleep one second before it calls the decorated function:

下一个示例似乎不太有用。 您为什么要放慢Python代码的速度? 可能最常见的用例是您希望对一个函数进行速率限制,该函数可以连续检查资源(例如网页)是否已更改。 @slow_down装饰器将在调用装饰函数之前Hibernate一秒钟:

To see the effect of the @slow_down decorator, you really need to run the example yourself:

要查看@slow_down装饰器的效果,您确实需要亲自运行该示例:

 >>> >>>  countdowncountdown (( 33 )
)
3
3
2
2
1
1
Liftoff!
Liftoff!

Note: The countdown() function is a recursive function. In other words, it’s a function calling itself. To learn more about recursive functions in Python, see our guide on Thinking Recursively in Python.

注意: countdown()函数是递归函数。 换句话说,这是一个调用自身的函数。 要了解有关Python中的递归函数的更多信息,请参阅《 Python中的递归思考》指南。

The @slow_down decorator always sleeps for one second. Later, you’ll see how to control the rate by passing an argument to the decorator.

@slow_down装饰器始终Hibernate一秒钟。 稍后 ,您将看到如何通过将参数传递给装饰器来控制速率。

注册插件 (Registering Plugins)

Decorators don’t have to wrap the function they’re decorating. They can also simply register that a function exists and return it unwrapped. This can be used, for instance, to create a light-weight plug-in architecture:

装饰器不必包装正在装饰的功能。 他们还可以简单地注册一个函数存在并返回未包装的函数。 例如,可以使用它来创建轻量级的插件体系结构:

The @register decorator simply stores a reference to the decorated function in the global PLUGINS dict. Note that you do not have to write an inner function or use @functools.wraps in this example because you are returning the original function unmodified.

@register装饰器只是在全局PLUGINS字典中存储对装饰函数的引用。 请注意,在此示例中,您不必编写内部函数或使用@functools.wraps ,因为您将返回未修改的原始函数。

The randomly_greet() function randomly chooses one of the registered functions to use. Note that the PLUGINS dictionary already contains references to each function object that is registered as a plugin:

randomly_greet()函数随机选择要使用的已注册函数之一。 请注意, PLUGINS词典已经包含对注册为插件的每个功能对象的引用:

 >>> >>>  PLUGINS
PLUGINS
{'say_hello': <function say_hello at 0x7f768eae6730>,
{'say_hello': <function say_hello at 0x7f768eae6730>,
 'be_awesome': <function be_awesome at 0x7f768eae67b8>}

 'be_awesome': <function be_awesome at 0x7f768eae67b8>}

>>> >>>  randomly_greetrandomly_greet (( "Alice""Alice" )
)
Using 'say_hello'
Using 'say_hello'
'Hello Alice'
'Hello Alice'

The main benefit of this simple plugin architecture is that you do not need to maintain a list of which plugins exist. That list is created when the plugins register themselves. This makes it trivial to add a new plugin: just define the function and decorate it with @register.

这种简单的插件体系结构的主要优点是,您无需维护存在的插件列表。 该列表在插件注册时创建。 这使得添加新插件变得微不足道:只需定义函数并使用@register装饰它。

If you are familiar with globals() in Python, you might see some similarities to how the plugin architecture works. globals() gives access to all global variables in the current scope, including your plugins:

如果您熟悉Python中的globals() ,则可能会看到一些与插件体系结构工作方式相似的地方。 globals()允许访问当前作用域中的所有全局变量,包括您的插件:

Using the @register decorator, you can create your own curated list of interesting variables, effectively hand-picking some functions from globals().

使用@register装饰器,您可以创建自己的@register有趣变量列表,从而有效地从globals()手动选择一些函数。

用户是否登录? (Is the User Logged In?)

The final example before moving on to some fancier decorators is commonly used when working with a web framework. In this example, we are using Flask to set up a /secret web page that should only be visible to users that are logged in or otherwise authenticated:

在使用Web框架时,通常使用最后一个更高级的装饰器示例。 在此示例中,我们使用Flask设置/secret网页,该网页仅对已登录或已通过身份验证的用户可见:

 from from flask flask import import FlaskFlask , , gg , , requestrequest , , redirectredirect , , url_for
url_for
import import functools
functools
app app = = FlaskFlask (( __name____name__ )

)

def def login_requiredlogin_required (( funcfunc ):
    ):
    """Make sure user is logged in before proceeding"""
    """Make sure user is logged in before proceeding"""
    @functools@functools .. wrapswraps (( funcfunc )
    )
    def def wrapper_login_requiredwrapper_login_required (( ** argsargs , , **** kwargskwargs ):
        ):
        if if gg .. user user is is NoneNone :
            :
            return return redirectredirect (( url_forurl_for (( "login""login" , , nextnext == requestrequest .. urlurl ))
        ))
        return return funcfunc (( ** argsargs , , **** kwargskwargs )
    )
    return return wrapper_login_required

wrapper_login_required

@app@app .. routeroute (( "/secret""/secret" )
)
@login_required
@login_required
def def secretsecret ():
    ():
    ...
...

While this gives an idea about how to add authentication to your web framework, you should usually not write these types of decorators yourself. For Flask, you can use the Flask-Login extension instead, which adds more security and functionality.

虽然这给出了有关如何向Web框架添加身份验证的想法,但通常不应该自己编写这些类型的装饰器。 对于Flask,您可以改用Flask-Login扩展 ,它增加了更多的安全性和功能。

花式装饰 (Fancy Decorators)

So far, you’ve seen how to create simple decorators. You already have a pretty good understanding of what decorators are and how they work. Feel free to take a break from this article to practice everything you’ve learned.

到目前为止,您已经了解了如何创建简单的装饰器。 您已经对什么是装饰器以及它们如何工作有了很好的了解。 随意在本文中休息一下,练习您所学到的一切。

In the second part of this tutorial, we’ll explore more advanced features, including how to use the following:

在本教程的第二部分中,我们将探索更多高级功能,包括如何使用以下功能:

装饰类 (Decorating Classes)

There are two different ways you can use decorators on classes. The first one is very close to what you have already done with functions: you can decorate the methods of a class. This was one of the motivations for introducing decorators back in the day.

在类上可以使用两种不同的装饰器。 第一个非常类似于您已经使用函数完成的工作:您可以修饰类的方法 。 这是在今天引入装饰工的动机之一

Some commonly used decorators that are even built-ins in Python are @classmethod, @staticmethod, and @property. The @classmethod and @staticmethod decorators are used to define methods inside a class namespace that are not connected to a particular instance of that class. The @property decorator is used to customize getters and setters for class attributes. Expand the box below for an example using these decorators.

一些常用的装饰器甚至内建在Python是@classmethod@staticmethod ,和@property@classmethod@staticmethod装饰器用于在类名称空间中定义未连接到该类的特定实例的方法。 @property装饰器用于自定义类属性的getter和setter 。 展开下面的框,查看使用这些装饰器的示例。

The following definition of a Circle class uses the @classmethod, @staticmethod, and @property decorators:

一的定义如下Circle类使用@classmethod@staticmethod@property装饰:

In this class:

在本课程中:

  • .cylinder_volume() is a regular method.
  • .radius is a mutable property: it can be set to a different value. However, by defining a setter method, we can do some error testing to make sure it’s not set to a nonsensical negative number. Properties are accessed as attributes without parentheses.
  • .area is an immutable property: properties without .setter() methods can’t be changed. Even though it is defined as a method, it can be retrieved as an attribute without parentheses.
  • .unit_circle() is a class method. It’s not bound to one particular instance of Circle. Class methods are often used as factory methods that can create specific instances of the class.
  • .pi() is a static method. It’s not really dependent on the Circle class, except that it is part of its namespace. Static methods can be called on either an instance or the class.
  • .cylinder_volume()是常规方法。
  • .radius是可变属性:可以将其设置为其他值。 但是,通过定义setter方法,我们可以进行一些错误测试,以确保未将其设置为无意义的负数。 属性作为没有括号的属性来访问。
  • .area是一个不可变的属性:没有.setter()方法的属性不能更改。 即使将其定义为方法,也可以将其检索为没有括号的属性。
  • .unit_circle()是一个类方法。 它没有绑定到Circle一个特定实例。 类方法通常用作可以创建类的特定实例的工厂方法。
  • .pi()是静态方法。 它实际上不是Circle类的依赖,只是它是其命名空间的一部分。 可以在实例或类上调用静态方法。

The Circle class can for example be used as follows:

例如,可以使用Circle类,如下所示:

 >>> >>>  c c = = CircleCircle (( 55 )
)
>>> >>>  cc .. radius
radius
5

5

>>> >>>  cc .. area
area
78.5398163375

78.5398163375

>>> >>>  cc .. radius radius = = 2
2
>>> >>>  cc .. area
area
12.566370614

12.566370614

>>> >>>  cc .. area area = = 100
100
AttributeError: can't set attribute

AttributeError: can't set attribute

>>> >>>  cc .. cylinder_volumecylinder_volume (( heightheight == 44 )
)
50.265482456

50.265482456

>>> >>>  cc .. radius radius = = -- 1
1
ValueError: Radius must be positive

ValueError: Radius must be positive

>>> >>>  c c = = CircleCircle .. unit_circleunit_circle ()
()
>>> >>>  cc .. radius
radius
1

1

>>> >>>  cc .. pipi ()
()
3.1415926535

3.1415926535

>>> >>>  CircleCircle .. pipi ()
()
3.1415926535
3.1415926535

Let’s define a class where we decorate some of its methods using the @debug and @timer decorators from earlier:

让我们定义一个类,在其中使用先前@debug@timer装饰器装饰其某些方法:

Using this class, you can see the effect of the decorators:

使用此类,您可以看到装饰器的效果:

 >>> >>>  tw tw = = TimeWasterTimeWaster (( 10001000 )
)
Calling __init__(<time_waster.TimeWaster object at 0x7efccce03908>, 1000)
Calling __init__(<time_waster.TimeWaster object at 0x7efccce03908>, 1000)
'__init__' returned None

'__init__' returned None

>>> >>>  twtw .. waste_timewaste_time (( 999999 )
)
Finished 'waste_time' in 0.3376 secs
Finished 'waste_time' in 0.3376 secs

The other way to use decorators on classes is to decorate the whole class. This is, for example, done in the new dataclasses module in Python 3.7:

在类上使用装饰器的另一种方法是装饰整个类 。 例如,这是在Python 3.7的new dataclasses模块中完成的:

The meaning of the syntax is similar to the function decorators. In the example above, you could have done the decoration by writing PlayingCard = dataclass(PlayingCard).

语法的含义类似于函数装饰器。 在上面的示例中,您可以通过编写PlayingCard = dataclass(PlayingCard)来完成装饰。

A common use of class decorators is to be a simpler alternative to some use-cases of metaclasses. In both cases, you are changing the definition of a class dynamically.

类装饰器的常见用法是替代一些元类用例。 在这两种情况下,您都是动态更改类的定义。

Writing a class decorator is very similar to writing a function decorator. The only difference is that the decorator will receive a class and not a function as an argument. In fact, all the decorators you saw above will work as class decorators. When you are using them on a class instead of a function, their effect might not be what you want. In the following example, the @timer decorator is applied to a class:

编写类装饰器与编写函数装饰器非常相似。 唯一的区别是装饰器将接收类而不是函数作为参数。 实际上, 您在上面看到的所有装饰器将用作类装饰器。 当您在类而不是函数上使用它们时,它们的效果可能不是您想要的。 在下面的示例中, @timer装饰器应用于一个类:

 from from decorators decorators import import timer

timer

@timer
@timer
class class TimeWasterTimeWaster :
    :
    def def __init____init__ (( selfself , , max_nummax_num ):
        ):
        selfself .. max_num max_num = = max_num

    max_num

    def def waste_timewaste_time (( selfself , , num_timesnum_times ):
        ):
        for for _ _ in in rangerange (( num_timesnum_times ):
            ):
            sumsum ([([ ii **** 2 2 for for i i in in rangerange (( selfself .. max_nummax_num )])
)])

Decorating a class does not decorate its methods. Recall that @timer is just shorthand for TimeWaster = timer(TimeWaster).

装饰类不会修饰其方法。 回想一下@timer只是TimeWaster = timer(TimeWaster)简写。

Here, @timer only measures the time it takes to instantiate the class:

在这里, @timer仅测量实例化类所花费的时间:

Later, you will see an example defining a proper class decorator, namely @singleton, which ensures that there is only one instance of a class.

稍后 ,您将看到一个示例,该示例定义了适当的类装饰器,即@singleton ,它确保了一个类只有一个实例。

嵌套装饰器 (Nesting Decorators)

You can apply several decorators to a function by stacking them on top of each other:

您可以通过将多个装饰器彼此堆叠来将其应用到一个函数:

 from from decorators decorators import import debugdebug , , do_twice

do_twice

@debug
@debug
@do_twice
@do_twice
def def greetgreet (( namename ):
    ):
    printprint (( ff "Hello "Hello  {name}{name} "" )
)

Think about this as the decorators being executed in the order they are listed. In other words, @debug calls @do_twice, which calls greet(), or debug(do_twice(greet())):

考虑一下装饰器是按照列出的顺序执行的。 换句话说, @debug调用@do_twice ,后者调用greet()debug(do_twice(greet()))

Observe the difference if we change the order of @debug and @do_twice:

如果我们更改@debug@do_twice的顺序, @debug观察差异:

 from from decorators decorators import import debugdebug , , do_twice

do_twice

@do_twice
@do_twice
@debug
@debug
def def greetgreet (( namename ):
    ):
    printprint (( ff "Hello "Hello  {name}{name} "" )
)

In this case, @do_twice will be applied to @debug as well:

在这种情况下, @do_twice也将应用于@debug

带参数的装饰器 (Decorators With Arguments)

Sometimes, it’s useful to pass arguments to your decorators. For instance, @do_twice could be extended to a @repeat(num_times) decorator. The number of times to execute the decorated function could then be given as an argument.

有时, 将参数传递给装饰器很有用。 例如, @do_twice可以扩展为@repeat(num_times)装饰器。 然后可以将执行修饰的函数的次数作为参数。

This would allow you to do something like this:

这将允许您执行以下操作:

 @repeat@repeat (( num_timesnum_times == 44 )
)
def def greetgreet (( namename ):
    ):
    printprint (( ff "Hello "Hello  {name}{name} "" )
)

Think about how you could achieve this.

考虑一下如何实现这一目标。

So far, the name written after the @ has referred to a function object that can be called with another function. To be consistent, you then need repeat(num_times=4) to return a function object that can act as a decorator. Luckily, you already know how to return functions! In general, you want something like the following:

到目前为止, @后面写的名称已指向一个可以用另一个函数调用的函数对象。 为了保持一致,然后需要repeat(num_times=4)以返回可以充当装饰器的函数对象。 幸运的是,您已经知道如何返回函数 ! 通常,您需要以下内容:

 def def repeatrepeat (( num_timesnum_times ):
    ):
    def def decorator_repeatdecorator_repeat (( funcfunc ):
        ):
        ...  ...  # Create and return a wrapper function
    # Create and return a wrapper function
    return return decorator_repeat
decorator_repeat

Typically, the decorator creates and returns an inner wrapper function, so writing the example out in full will give you an inner function within an inner function. While this might sound like the programming equivalent of the Inception movie, we’ll untangle it all in a moment:

通常,装饰器创建并返回一个内部包装器函数,因此完整编写示例将为您提供内部函数内的内部函数。 尽管这听起来像是《 盗梦空间》电影的编程等效内容,但我们稍后将对其进行梳理:

It looks a little messy, but we have only put the same decorator pattern you have seen many times by now inside one additional def that handles the arguments to the decorator. Let’s start with the innermost function:

看起来有些混乱,但是我们只将您到目前为止已经看到过多次的相同装饰器模式放入一个附加的def ,该def处理该装饰器的参数。 让我们从最里面的功能开始:

 def def wrapper_repeatwrapper_repeat (( ** argsargs , , **** kwargskwargs ):
    ):
    for for _ _ in in rangerange (( num_timesnum_times ):
        ):
        value value = = funcfunc (( ** argsargs , , **** kwargskwargs )
    )
    return return value
value

This wrapper_repeat() function takes arbitrary arguments and returns the value of the decorated function, func(). This wrapper function also contains the loop that calls the decorated function num_times times. This is no different from the earlier wrapper functions you have seen, except that it is using the num_times parameter that must be supplied from the outside.

wrapper_repeat()函数采用任意参数,并返回修饰后的函数func() 。 该包装函数还包含调用装饰函数num_times次的循环。 这与您之前看到的包装函数没有什么不同,只不过它使用的num_times参数必须从外部提供。

One step out, you’ll find the decorator function:

迈出第一步,您将找到装饰器功能:

Again, decorator_repeat() looks exactly like the decorator functions you have written earlier, except that it’s named differently. That’s because we reserve the base name—repeat()—for the outermost function, which is the one the user will call.

同样, decorator_repeat()看起来与您先前编写的装饰器函数完全相同,只是其名称不同。 这是因为我们保留了最外层函数的基本名称repeat() ,这是用户将调用的函数。

As you have already seen, the outermost function returns a reference to the decorator function:

如您所见,最外面的函数返回对装饰器函数的引用:

 def def repeatrepeat (( num_timesnum_times ):
    ):
    def def decorator_repeatdecorator_repeat (( funcfunc ):
        ):
        ...
    ...
    return return decorator_repeat
decorator_repeat

There are a few subtle things happening in the repeat() function:

repeat()函数中发生了一些细微的事情:

  • Defining decorator_repeat() as an inner function means that repeat() will refer to a function object—decorator_repeat. Earlier, we used repeat without parentheses to refer to the function object. The added parentheses are necessary when defining decorators that take arguments.
  • The num_times argument is seemingly not used in repeat() itself. But by passing num_times a closure is created where the value of num_times is stored until it will be used later by wrapper_repeat().
  • decorator_repeat()定义为内部函数意味着repeat()将引用一个函数对象decorator_repeat 。 之前,我们使用无括号的repeat来引用函数对象。 定义带有参数的修饰符时,添加的括号是必需的。
  • num_times参数似乎未在repeat()本身中使用。 但是通过传递num_times ,将num_times一个闭包 ,其中存储num_times的值,直到稍后将由wrapper_repeat()

With everything set up, let’s see if the results are as expected:

完成所有设置后,让我们看看结果是否符合预期:

 >>> >>>  greetgreet (( "World""World" )
)
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World

Just the result we were aiming for.

只是我们想要的结果。

都请,但不要介意面包 (Both Please, But Never Mind the Bread)

With a little bit of care, you can also define decorators that can be used both with and without arguments. Most likely, you don’t need this, but it is nice to have the flexibility.

稍加注意,您还可以定义可以使用带参数和不带参数的装饰器 。 最有可能的是,您不需要此功能,但是拥有灵活性很高兴。

As you saw in the previous section, when a decorator uses arguments, you need to add an extra outer function. The challenge is for your code to figure out if the decorator has been called with or without arguments.

如上一节所述,当装饰器使用参数时,您需要添加一个额外的外部函数。 挑战在于您的代码要确定是否已使用参数调用装饰器。

Since the function to decorate is only passed in directly if the decorator is called without arguments, the function must be an optional argument. This means that the decorator arguments must all be specified by keyword. You can enforce this with the special * syntax, which means that all following parameters are keyword-only:

由于要装饰的函数仅在不带参数的情况下调用装饰器时才直接传递,因此该函数必须是可选参数。 这意味着装饰器参数必须全部由关键字指定。 您可以使用特殊的*语法强制执行此操作,这意味着以下所有参数都是仅关键字的

Here, the _func argument acts as a marker, noting whether the decorator has been called with arguments or not:

在这里, _func参数充当标记,指出装饰器是否已使用参数调用:

  1. If name has been called without arguments, the decorated function will be passed in as _func. If it has been called with arguments, then _func will be None, and some of the keyword arguments may have been changed from their default values. The * in the argument list means that the remaining arguments can’t be called as positional arguments.
  2. In this case, the decorator was called with arguments. Return a decorator function that can read and return a function.
  3. In this case, the decorator was called without arguments. Apply the decorator to the function immediately.
  1. 如果没有参数调用了name ,则装饰函数将作为_func 。 如果已使用参数调用了它,则_func将为None ,并且某些关键字参数可能已更改为其默认值。 参数列表中的*表示其余参数不能称为位置参数。
  2. 在这种情况下,用参数调用装饰器。 返回一个装饰器函数,该函数可以读取和返回一个函数。
  3. 在这种情况下,装饰器被调用而没有参数。 立即将装饰器应用于该函数。

Using this boilerplate on the @repeat decorator in the previous section, you can write the following:

在上一节的@repeat装饰器上使用此样板,可以编写以下内容:

 def def repeatrepeat (( _func_func == NoneNone , , ** , , num_timesnum_times == 22 ):
):
def def decorator_repeatdecorator_repeat (( funcfunc ):
        ):
        @functools@functools .. wrapswraps (( funcfunc )
        )
        def def wrapper_repeatwrapper_repeat (( ** argsargs , , **** kwargskwargs ):
            ):
            for for _ _ in in rangerange (( num_timesnum_times ):
                ):
                value value = = funcfunc (( ** argsargs , , **** kwargskwargs )
            )
            return return value
        value
        return return wrapper_repeat

wrapper_repeat

if if _func _func is is NoneNone :
:
return return decorator_repeat
decorator_repeat
elseelse :
:
return return decorator_repeatdecorator_repeat (( _func_func )
)

Compare this with the original @repeat. The only changes are the added _func parameter and the ifelse at the end.

将此与原始@repeat进行比较。 唯一的变化是添加的_func参数和if - else结尾。

Recipe 9.6 of the excellent Python Cookbook shows an alternative solution using functools.partial().

优秀的Python Cookbook的 9.6节显示了使用functools.partial()的替代解决方案。

These examples show that @repeat can now be used with or without arguments:

这些示例表明, @repeat现在可以带或不带参数使用:

Recall that the default value of num_times is 2:

回想一下num_times的默认值为2:

 >>> >>>  say_wheesay_whee ()
()
Whee!
Whee!
Whee!

Whee!

>>> >>>  greetgreet (( "Penny""Penny" )
)
Hello Penny
Hello Penny
Hello Penny
Hello Penny
Hello Penny
Hello Penny

有状态的装饰者 (Stateful Decorators)

Sometimes, it’s useful to have a decorator that can keep track of state. As a simple example, we will create a decorator that counts the number of times a function is called.

有时,拥有一个可以跟踪state的装饰器很有用。 作为一个简单的示例,我们将创建一个装饰器,该装饰器对调用函数的次数进行计数。

Note: In the beginning of this guide, we talked about pure functions returning a value based on given arguments. Stateful decorators are quite the opposite, where the return value will depend on the current state, as well as the given arguments.

注意:本指南的开头 ,我们讨论了基于给定参数返回值的纯函数。 有状态的装饰器则相反,其中返回值将取决于当前状态以及给定的参数。

In the next section, you will see how to use classes to keep state. But in simple cases, you can also get away with using function attributes:

在下一部分中 ,您将看到如何使用类来保持状态。 但是在简单的情况下,您也可以使用函数属性

The state—the number of calls to the function—is stored in the function attribute .num_calls on the wrapper function. Here is the effect of using it:

状态(对函数的调用次数)存储在包装函数的函数属性.num_calls 。 这是使用它的效果:

 >>> >>>  say_wheesay_whee ()
()
Call 1 of 'say_whee'
Call 1 of 'say_whee'
Whee!

Whee!

>>> >>>  say_wheesay_whee ()
()
Call 2 of 'say_whee'
Call 2 of 'say_whee'
Whee!

Whee!

>>> >>>  say_wheesay_whee .. num_calls
num_calls
2
2

类作为装饰器 (Classes as Decorators)

The typical way to maintain state is by using classes. In this section, you’ll see how to rewrite the @count_calls example from the previous section using a class as a decorator.

维持状态的典型方法是使用类 。 在本节中,您将看到如何使用类作为装饰器重写上一节中的@count_calls示例。

Recall that the decorator syntax @my_decorator is just an easier way of saying func = my_decorator(func). Therefore, if my_decorator is a class, it needs to take func as an argument in its .__init__() method. Furthermore, the class needs to be callable so that it can stand in for the decorated function.

回想一下装饰器语法@my_decorator只是说func = my_decorator(func)一种简单方法。 因此,如果my_decorator是一个类,则需要在其.__init__()方法中将func用作参数。 此外,该类必须是可调用的,以便它可以代表修饰的函数。

For a class to be callable, you implement the special .__call__() method:

对于可调用的类,请实现特殊的.__call__()方法:

The .__call__() method is executed each time you try to call an instance of the class:

每次您尝试调用该类的实例时,都会执行.__call__()方法:

 >>> >>>  counter counter = = CounterCounter ()
()
>>> >>>  countercounter ()
()
Current count is 1

Current count is 1

>>> >>>  countercounter ()
()
Current count is 2

Current count is 2

>>> >>>  countercounter .. count
count
2
2

Therefore, a typical implementation of a decorator class needs to implement .__init__() and .__call__():

因此,装饰器类的典型实现需要实现.__init__().__call__()

The .__init__() method must store a reference to the function and can do any other necessary initialization. The .__call__() method will be called instead of the decorated function. It does essentially the same thing as the wrapper() function in our earlier examples. Note that you need to use the functools.update_wrapper() function instead of @functools.wraps.

.__init__()方法必须存储对该函数的引用,并且可以执行任何其他必要的初始化。 将.__call__()方法而不是修饰函数。 它与我们之前的示例中的wrapper()函数基本上具有相同的作用。 请注意,您需要使用functools.update_wrapper()函数而不是@functools.wraps

This @CountCalls decorator works the same as the one in the previous section:

@CountCalls装饰器的工作原理与上一节中的装饰器相同:

 >>> >>>  say_wheesay_whee ()
()
Call 1 of 'say_whee'
Call 1 of 'say_whee'
Whee!

Whee!

>>> >>>  say_wheesay_whee ()
()
Call 2 of 'say_whee'
Call 2 of 'say_whee'
Whee!

Whee!

>>> >>>  say_wheesay_whee .. num_calls
num_calls
2
2

更多真实世界的例子 (More Real World Examples)

We’ve come a far way now, having figured out how to create all kinds of decorators. Let’s wrap it up, putting our newfound knowledge into creating a few more examples that might actually be useful in the real world.

我们已经走了很长一段路,已经找到了如何创建各种装饰器的方法。 总结一下,将我们新发现的知识用于创建更多实际在现实世界中有用的示例。

重新审视代码变慢 (Slowing Down Code, Revisited)

As noted earlier, our previous implementation of @slow_down always sleeps for one second. Now you know how to add parameters to decorators, so let’s rewrite @slow_down using an optional rate argument that controls how long it sleeps:

如前所述,我们以前的@slow_down实现始终Hibernate一秒钟。 现在您知道了如何向装饰器添加参数,让我们使用一个可选的rate参数重写@slow_down ,该参数控制其Hibernate时间:

We’re using the boilerplate introduced in the Both Please, But Never Mind the Bread section to make @slow_down callable both with and without arguments. The same recursive countdown() function as earlier now sleeps two seconds between each count:

我们正在使用“ 请两个,但不要介意面包”一节中介绍的样板以使@slow_down可以带或不带参数调用。 同一递归countdown()函数如前面现在睡每个计数之间2秒:

 @slow_down@slow_down (( raterate == 22 )
)
def def countdowncountdown (( from_numberfrom_number ):
    ):
    if if from_number from_number < < 11 :
        :
        printprint (( "Liftoff!""Liftoff!" )
    )
    elseelse :
        :
        printprint (( from_numberfrom_number )
        )
        countdowncountdown (( from_number from_number - - 11 )
)

As before, you must run the example yourself to see the effect of the decorator:

和以前一样,您必须自己运行示例才能看到装饰器的效果:

创建单例 (Creating Singletons)

A singleton is a class with only one instance. There are several singletons in Python that you use frequently, including None, True, and False. It is the fact that None is a singleton that allows you to compare for None using the is keyword, like you saw in the Both Please section:

单例是只有一个实例的类。 Python中经常使用几个单例,包括NoneTrueFalse 。 事实是, None是一个单例,它使您可以使用is关键字比较None ,就像您在Both Please部分中看到的那样:

 if if _func _func is is NoneNone :
    :
    return return decorator_name
decorator_name
elseelse :
    :
    return return decorator_namedecorator_name (( _func_func )
)

Using is returns True only for objects that are the exact same instance. The following @singleton decorator turns a class into a singleton by storing the first instance of the class as an attribute. Later attempts at creating an instance simply return the stored instance:

使用is仅对于完全相同的实例的对象返回True 。 下面的@singleton装饰器通过将类的第一个实例存储为属性,将类转换为单例。 以后创建实例的尝试仅返回存储的实例:

As you see, this class decorator follows the same template as our function decorators. The only difference is that we are using cls instead of func as the parameter name to indicate that it is meant to be a class decorator.

如您所见,此类装饰器遵循与函数装饰器相同的模板。 唯一的区别是,我们使用cls代替func作为参数名称,以表明它打算用作类装饰器。

Let’s see if it works:

让我们看看它是否有效:

 >>> >>>  first_one first_one = = TheOneTheOne ()
()
>>> >>>  another_one another_one = = TheOneTheOne ()

()

>>> >>>  idid (( first_onefirst_one )
)
140094218762280

140094218762280

>>> >>>  idid (( another_oneanother_one )
)
140094218762280

140094218762280

>>> >>>  first_one first_one is is another_one
another_one
True
True

It seems clear that first_one is indeed the exact same instance as another_one.

显然, first_one确实是与another_one完全相同的实例。

Note: Singleton classes are not really used as often in Python as in other languages. The effect of a singleton is usually better implemented as a global variable in a module.

注意: Singleton类在Python中的使用并不像在其他语言中那样频繁。 通常,将单例的效果更好地实现为模块中的全局变量。

缓存返回值 (Caching Return Values)

Decorators can provide a nice mechanism for caching and memoization. As an example, let’s look at a recursive definition of the Fibonacci sequence:

装饰器可以提供一种很好的缓存和备注机制。 例如,让我们看一下斐波那契数列递归定义:

While the implementation is simple, its runtime performance is terrible:

虽然实现很简单,但是其运行时性能却很糟糕:

 >>> >>>  fibonaccifibonacci (( 1010 )
)
<Lots of output from count_calls>
<Lots of output from count_calls>
55

55

>>> >>>  fibonaccifibonacci .. num_calls
num_calls
177
177

To calculate the tenth Fibonacci number, you should really only need to calculate the preceding Fibonacci numbers, but this implementation somehow needs a whopping 177 calculations. It gets worse quickly: 21891 calculations are needed for fibonacci(20) and almost 2.7 million calculations for the 30th number. This is because the code keeps recalculating Fibonacci numbers that are already known.

要计算第十个斐波那契数,您实际上只需要计算前面的斐波那契数,但是此实现需要某种程度的177次计算。 它很快变得越来越糟: fibonacci(20)需要21891次计算,而第30个数字需要270万次计算。 这是因为代码会不断重新计算已知的斐波那契数。

The usual solution is to implement Fibonacci numbers using a for loop and a lookup table. However, simple caching of the calculations will also do the trick:

通常的解决方案是使用for循环和查找表来实现斐波那契数。 但是,简单的计算缓存也可以解决问题:

The cache works as a lookup table, so now fibonacci() only does the necessary calculations once:

缓存用作查找表,因此fibonacci()现在仅进行必要的计算一次:

 >>> >>>  fibonaccifibonacci (( 1010 )
)
Call 1 of 'fibonacci'
Call 1 of 'fibonacci'
...
...
Call 11 of 'fibonacci'
Call 11 of 'fibonacci'
55

55

>>> >>>  fibonaccifibonacci (( 88 )
)
21
21

Note that in the final call to fibonacci(8), no new calculations were needed, since the eighth Fibonacci number had already been calculated for fibonacci(10).

请注意,在最后调用fibonacci(8) ,不需要进行新的计算,因为已经为fibonacci(10)计算了第八个斐波那契数。

In the standard library, a Least Recently Used (LRU) cache is available as @functools.lru_cache.

在标准库中, 最近使用最少的(LRU)缓存可用作@functools.lru_cache

This decorator has more features than the one you saw above. You should use @functools.lru_cache instead of writing your own cache decorator:

这个装饰器比您上面看到的具有更多功能。 您应该使用@functools.lru_cache而不是编写自己的缓存装饰器:

The maxsize parameter specifies how many recent calls are cached. The default value is 128, but you can specify maxsize=None to cache all function calls. However, be aware that this can cause memory problems if you are caching many large objects.

maxsize参数指定缓存最近的调用数。 缺省值为128,但是您可以指定maxsize=None来缓存所有函数调用。 但是,请注意,如果要缓存许多大对象,这可能会导致内存问题。

You can use the .cache_info() method to see how the cache performs, and you can tune it if needed. In our example, we used an artificially small maxsize to see the effect of elements being removed from the cache:

您可以使用.cache_info()方法查看缓存的性能,并且可以根据需要对其进行调整。 在我们的示例中,我们使用人为的小maxsize来查看从缓存中删除元素的效果:

 >>> >>>  fibonaccifibonacci (( 1010 )
)
Calculating fibonacci(10)
Calculating fibonacci(10)
Calculating fibonacci(9)
Calculating fibonacci(9)
Calculating fibonacci(8)
Calculating fibonacci(8)
Calculating fibonacci(7)
Calculating fibonacci(7)
Calculating fibonacci(6)
Calculating fibonacci(6)
Calculating fibonacci(5)
Calculating fibonacci(5)
Calculating fibonacci(4)
Calculating fibonacci(4)
Calculating fibonacci(3)
Calculating fibonacci(3)
Calculating fibonacci(2)
Calculating fibonacci(2)
Calculating fibonacci(1)
Calculating fibonacci(1)
Calculating fibonacci(0)
Calculating fibonacci(0)
55

55

>>> >>>  fibonaccifibonacci (( 88 )
)
21

21

>>> >>>  fibonaccifibonacci (( 55 )
)
Calculating fibonacci(5)
Calculating fibonacci(5)
Calculating fibonacci(4)
Calculating fibonacci(4)
Calculating fibonacci(3)
Calculating fibonacci(3)
Calculating fibonacci(2)
Calculating fibonacci(2)
Calculating fibonacci(1)
Calculating fibonacci(1)
Calculating fibonacci(0)
Calculating fibonacci(0)
5

5

>>> >>>  fibonaccifibonacci (( 88 )
)
Calculating fibonacci(8)
Calculating fibonacci(8)
Calculating fibonacci(7)
Calculating fibonacci(7)
Calculating fibonacci(6)
Calculating fibonacci(6)
21

21

>>> >>>  fibonaccifibonacci (( 55 )
)
5

5

>>> >>>  fibonaccifibonacci .. cache_infocache_info ()
()
CacheInfo(hits=17, misses=20, maxsize=4, currsize=4)
CacheInfo(hits=17, misses=20, maxsize=4, currsize=4)

添加有关单位的信息 (Adding Information About Units)

The following example is somewhat similar to the Registering Plugins example from earlier, in that it does not really change the behavior of the decorated function. Instead, it simply adds unit as a function attribute:

下面的示例与先前的注册插件示例有些相似,因为它并没有真正改变修饰函数的行为。 相反,它只是将unit作为函数属性添加:

The following example calculates the volume of a cylinder based on its radius and height in centimeters:

以下示例根据圆柱体的半径和高度(以厘米为单位)计算圆柱体的体积:

 import import math

math

@set_unit@set_unit (( "cm^3""cm^3" )
)
def def volumevolume (( radiusradius , , heightheight ):
    ):
    return return mathmath .. pi pi * * radiusradius **** 2 2 * * height
height

This .unit function attribute can later be accessed when needed:

以后可以在需要时访问此.unit函数属性:

Note that you could have achieved something similar using function annotations:

请注意,使用功能注释可以实现类似的效果:

 import import math

math

def def volumevolume (( radiusradius , , heightheight ) ) -> -> "cm^3""cm^3" :
:
return return mathmath .. pi pi * * radiusradius **** 2 2 * * height
height

However, since annotations are used for type hints, it would be hard to combine such units as annotations with static type checking.

但是,由于注释用于类型提示 ,因此很难将诸如注释之类的单元与静态类型检查结合在一起。

Units become even more powerful and fun when connected with a library that can convert between units. One such library is pint. With pint installed (pip install Pint), you can for instance convert the volume to cubic inches or gallons:

与可在单元之间转换的库连接时,单元变得更加强大和有趣。 pint就是其中之一。 随着pint装( pip install Pint ),可以为实例转换的体积立方英寸或加仑:

You could also modify the decorator to return a pint Quantity directly. Such a Quantity is made by multiplying a value with the unit. In pint, units must be looked up in a UnitRegistry. The registry is stored as a function attribute to avoid cluttering the namespace:

您还可以修改装饰器,以直接返回一pint Quantity 。 这种Quantity是通过将值乘以单位得出的。 pint ,必须在UnitRegistry查找单位。 注册表存储为函数属性,以避免使名称空间混乱:

 def def use_unituse_unit (( unitunit ):
    ):
    """Have a function return a Quantity with given unit"""
    """Have a function return a Quantity with given unit"""
    use_unituse_unit .. ureg ureg = = pintpint .. UnitRegistryUnitRegistry ()
    ()
    def def decorator_use_unitdecorator_use_unit (( funcfunc ):
        ):
        @functools@functools .. wrapswraps (( funcfunc )
        )
        def def wrapper_use_unitwrapper_use_unit (( ** argsargs , , **** kwargskwargs ):
            ):
            value value = = funcfunc (( ** argsargs , , **** kwargskwargs )
            )
            return return value value * * use_unituse_unit .. uregureg (( unitunit )
        )
        return return wrapper_use_unit
    wrapper_use_unit
    return return decorator_use_unit

decorator_use_unit

@use_unit@use_unit (( "meters per second""meters per second" )
)
def def average_speedaverage_speed (( distancedistance , , durationduration ):
    ):
    return return distance distance / / duration
duration

With the @use_unit decorator, converting units is practically effortless:

使用@use_unit装饰器,转换单位几乎不费力:

验证JSON (Validating JSON)

Let’s look at one last use case. Take a quick look at the following Flask route handler:

让我们看一下最后一个用例。 快速浏览以下Flask路由处理程序:

 @app@app .. routeroute (( "/grade""/grade" , , methodsmethods == [[ "POST""POST" ])
])
def def update_gradeupdate_grade ():
    ():
    json_data json_data = = requestrequest .. get_jsonget_json ()
    ()
    if if "student_id" "student_id" not not in in json_datajson_data :
        :
        abortabort (( 400400 )
    )
    # Update database
    # Update database
    return return "success!"
"success!"

Here we ensure that the key student_id is part of the request. Although this validation works, it really does not belong in the function itself. Plus, perhaps there are other routes that use the exact same validation. So, let’s keep it DRY and abstract out any unnecessary logic with a decorator. The following @validate_json decorator will do the job:

在这里,我们确保键student_id是请求的一部分。 尽管此验证有效,但实际上不属于该函数本身。 另外,也许还有其他路线使用完全相同的验证。 因此,让它保持DRY并使用装饰器抽象出任何不必要的逻辑。 以下@validate_json装饰器将完成此工作:

In the above code, the decorator takes a variable length list as an argument so that we can pass in as many string arguments as necessary, each representing a key used to validate the JSON data:

在上面的代码中,装饰器将可变长度列表作为参数,以便我们可以根据需要传入尽可能多的字符串参数,每个参数均表示用于验证JSON数据的密钥:

  1. The list of keys that must be present in the JSON is given as arguments to the decorator.
  2. The wrapper function validates that each expected key is present in the JSON data.
  1. JSON中必须存在的键列表作为装饰器的参数给出。
  2. 包装函数验证JSON数据中是否存在每个预期的密钥。

The route handler can then focus on its real job—updating grades—as it can safely assume that JSON data are valid:

然后,路由处理程序可以专注于其实际工作(更新等级),因为它可以安全地假定JSON数据有效:

 @app@app .. routeroute (( "/grade""/grade" , , methodsmethods == [[ "POST""POST" ])
])
@validate_json@validate_json (( "student_id""student_id" )
)
def def update_gradeupdate_grade ():
    ():
    json_data json_data = = requestrequest .. get_jsonget_json ()
    ()
    # Update database.
    # Update database.
    return return "success!"
"success!"

结论 (Conclusion)

This has been quite a journey! You started this tutorial by looking a little closer at functions, particularly how they can be defined inside other functions and passed around just like any other Python object. Then you learned about decorators and how to write them such that:

这是一段漫长的旅程! 在开始本教程时,我们先仔细研究了函数,尤其是如何在其他函数中定义它们并像其他任何Python对象一样传递它们。 然后,您了解了装饰器以及如何编写它们,使得:

  • They can be reused.
  • They can decorate functions with arguments and return values.
  • They can use @functools.wraps to look more like the decorated function.
  • 它们可以重复使用。
  • 他们可以用参数装饰函数并返回值。
  • 他们可以使用@functools.wraps看起来更像装饰函数。

In the second part of the tutorial, you saw more advanced decorators and learned how to:

在本教程的第二部分中,您看到了更多高级装饰器,并学习了如何:

  • Decorate classes
  • Nest decorators
  • Add arguments to decorators
  • Keep state within decorators
  • Use classes as decorators
  • 装饰类
  • 巢式装饰
  • 向装饰器添加参数
  • 在装饰器中保持状态
  • 使用类作为装饰器

You saw that, to define a decorator, you typically define a function returning a wrapper function. The wrapper function uses *args and **kwargs to pass on arguments to the decorated function. If you want your decorator to also take arguments, you need to nest the wrapper function inside another function. In this case, you usually end up with three return statements.

您已经看到,要定义装饰器,通常会定义一个返回包装函数的函数。 包装函数使用*args**kwargs将参数传递给装饰函数。 如果希望装饰器也接受参数,则需要将包装器函数嵌套在另一个函数中。 在这种情况下,您通常以三个return语句结束。

You can find the code from this tutorial online.

您可以在线从本教程中找到代码

进一步阅读 (Further Reading)

If you are still looking for more, our book Python Tricks has a section on decorators, as does the Python Cookbook by David Beazley and Brian K. Jones.

如果您仍在寻找更多内容,那么我们的书Python Tricks中有关于装饰器的部分,David Beazley和Brian K. Jones所著的Python Cookbook也是如此。

For a deep dive into the historical discussion on how decorators should be implemented in Python, see PEP 318 as well as the Python Decorator Wiki. More examples of decorators can be found in the Python Decorator Library.

要深入了解有关如何在Python中实现装饰器的历史讨论,请参阅PEP 318Python Decorator Wiki 。 装饰器的更多示例可以在Python Decorator Library中找到

Also, we’ve put together a short & sweet Python decorators cheat sheet for you:

另外,我们为您整理了一个简短的Python装饰器备忘单:

Decorators Cheat Sheet: Click here to get access to a free 3-page Python decorators cheat sheet that summarizes the techniques explained in this tutorial.

Decorators备忘单: 单击此处可访问免费的3页Python装饰器备忘单 ,其中总结了本教程中介绍的技术。

翻译自: https://www.pybloggers.com/2018/08/primer-on-python-decorators/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值