English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Python Basic Tutorial

Python Flow Control

Python Functions

Python Data Types

Python File Operations

Python Objects and Classes

Python Date and Time

Advanced Python Knowledge

Python Reference Manual

Python Decorators

Decorators accept a function, add some features, and return it. In this article, you will learn how to create decorators and why you should use them.

What are decorators in Python?

Python has an interesting feature calledDecorators, which can add functionality to existing code.

This is also calledMetaprogramming,Because part of the program tries to modify another part of the program at compile time.

Prerequisites for learning decorators

To understand decorators, we must first understand some basic knowledge of Python.

We must accept the fact that everything in Python isObjects. The names we define are just identifiers bound to these objects.FunctionsThis also applies; they are objects (with properties). Different names can be bound to the same functional object.

This is an example.

def first(msg):
    print(msg)    
first("Hello")
second = first
second("Hello")

When you run the code, the two functions first and second give the same output. Here, the names first and second refer to the same functional object.

Now, doesn't it seem a bit complicated that you can pass a function as a parameter to another function?

If you have used functions like map, filter, and reduce in Python, then you already know this.

This type of function, which takes other functions as parameters, is also calledHigher-order functions. This is an instance of such a function.

def inc(x):
    return x + 1
def dec(x):
    return x - 1
def operate(func, x):
    result = func(x)
    return result

We call the function as follows.

>>> operate(inc,3)
4
>>> operate(dec,3)
2

In addition, a function can return another function.

def is_called():
    def is_returned():
        print("Hello")
    return is_returned
new = is_called()
# Output "Hello"
new()

Here, is_returned() is a nested function that defines and returns each time we call is_drawn().

Finally, we must understandClosure in Python.

Return to decorators

Functions and methods are calledCallable,Because they can be called.

In fact, any object that implements the special method __call__() is called callable. Therefore, in the most basic sense, decorators are callable and can return callables.

In essence, decorators accept a function, add some functionality, and return it.

def make_pretty(func):
    def inner():
        print("I am decorated")
        func()
    return inner
def ordinary():
    print("I am an ordinary function")

When running the following code in the shell,

>>> ordinary()
I am an ordinary function
>>> # Let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I am decorated
I am an ordinary function

In the example shown above, make_pretty() is a decorator. In the assignment step.

pretty = make_pretty(ordinary)

The function ordinary() is decorated, and the returned function is named pretty.

We can see that the decorator function adds some new features to the original function. This is similar to wrapping a gift. The decorator acts as the wrapper. The nature of the item being decorated (the gift inside) does not change. But now, it looks pretty (since it has been decorated).

通常,我们装饰一个函数并将其重新分配为

ordinary = make_pretty(ordinary).

This is a common construction, so Python has syntax to simplify this.

We can use the @ symbol along with the decorator function name and place it above the definition of the function to be decorated. For example,

@make_pretty
def ordinary():
    print("I am an ordinary function")

is equivalent to

def ordinary():
    print("I am an ordinary function")
ordinary = make_pretty(ordinary)

This is just syntactic sugar for implementing decorators.

Decorate a function with parameters

The above decorator is simple and only applies to functions with no parameters. What should we do if our function has parameters as shown below?

def divide(a, b):
    return a/b

This function has two parameters,aandb. We know that if we willbAn error will occur if passed 0.

>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

Now let's make a decorator to check if this will cause an error.

def smart_divide(func):
   def inner(a, b):
      print("I want to divide", a, "and", b)
      if b == 0:
         print("Oh no! Cannot divide")
         return
      return func(a, b)
   return inner
@smart_divide
def divide(a, b):
    return a/b

If an error occurs, this new implementation will return None.

>>> divide(2,5)
I want to do division 2 and 5
0.4
>>> divide(2,0)
I want to do division 2 and 0
Oops! Can't divide

In this way, we can decorate functions with parameters.

Perceptive observers will notice that the parameters of the nested function inside the inner() decorator are the same as those of the function it decorates. Considering this, now we can make the generic decorator usable with an arbitrary number of parameters.

In Python, this magic is achieved by completing the function(*args, **kwargs).So, args are the positional arguments,tupleInstead of positional arguments, use keywords arguments,dictionary.An example of such a decorator is.

def works_for_all(func):
    def inner(*args, **kwargs):
        I can decorate any function
        return func(*args, **kwargs)
    return inner

Python chaining decorators

Multiple decorators can be chained in Python.

This means that a function can be decorated multiple times (or the same) with different decorators. We just need to place the decorators above the required function.

def star(func):
    def inner(*args, **kwargs):
        print(""*" * 30)
        func(*args, **kwargs)
        print(""*" * 30)
    return inner
def percent(func):
    def inner(*args, **kwargs):
        print("%%") * 30)
        func(*args, **kwargs)
        print("%%") * 30)
    return inner
@star
@percent
def printer(msg):
    print(msg)
printer("Hello")

This will give the output.

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

the above syntax,

@star
@percent
def printer(msg):
    print(msg)

is equivalent to

def printer(msg):
    print(msg)
printer = star(percent(printer))

The order of the decorators is important. If we reverse the order,

@percent
@star
def printer(msg):
    print(msg)

The execution will occur

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%