Python装饰器

在 Python 中,装饰器是一种设计模式,它允许你通过将一个函数包装在另一个函数中来修改其功能。

外部函数被称为装饰器,它接受原始函数作为参数并返回其修改版本。


学习装饰器的先决条件

在学习装饰器之前,我们需要理解一些与 Python 函数相关的重要概念。另外,请记住,Python 中的一切都是对象,即使函数也是对象。

嵌套函数

我们可以将一个函数包含在另一个函数中,这被称为嵌套函数。例如:

def outer(x):
    def inner(y):
        return x + y
    return inner

add_five = outer(5)
result = add_five(6)
print(result)  # prints 11

# Output: 11

在这里,我们在 outer() 函数内部创建了 inner() 函数。

将函数作为参数传递

在 Python 中,我们可以将一个函数作为参数传递给另一个函数。例如:

def add(x, y):
    return x + y

def calculate(func, x, y):
    return func(x, y)

result = calculate(add, 4, 6)
print(result)  # prints 10

输出

10

在上面的示例中,calculate() 函数接受一个函数作为其参数。在调用 calculate() 时,我们传递了 add() 函数作为参数。

calculate() 函数中,参数:funcxy 分别变为 add46

因此,func(x, y) 变为 add(4, 6),返回 10

将函数作为值返回

在 Python 中,我们也可以将函数作为返回值返回。例如:

def greeting(name):
    def hello():
        return "Hello, " + name + "!"
    return hello

greet = greeting("Atlantis")
print(greet())  # prints "Hello, Atlantis!"

# Output: Hello, Atlantis!

在上面的示例中,return hello 语句返回内部的 hello() 函数。现在,这个函数被赋值给 greet 变量。

这就是为什么当我们像函数一样调用 greet() 时,会得到输出。


Python装饰器

如前所述,Python 装饰器是一个接受函数并添加一些功能后返回该函数的函数。

事实上,任何实现特殊 __call__() 方法的对象都被称为可调用对象。所以,从最基本的意义上讲,装饰器是一个返回可调用对象的可调用对象

基本上,装饰器接受一个函数,添加一些功能并返回它。

def make_pretty(func):
    def inner():
        print("I got decorated")
        func()
    return inner


def ordinary():
    print("I am ordinary")

# Output: I am ordinary

在这里,我们创建了两个函数

  • ordinary() 打印 "I am ordinary"
  • make_pretty() 接受一个函数作为参数,并有一个名为 inner() 的嵌套函数,并返回内部函数。

我们正常调用 ordinary() 函数,因此我们得到输出 "I am ordinary"。现在,让我们使用装饰器函数调用它。

def make_pretty(func):
    # define the inner function 
    def inner():
        # add some additional behavior to decorated function
        print("I got decorated")

        # call original function
        func()
    # return the inner function
    return inner

# define ordinary function
def ordinary():
    print("I am ordinary")
    
# decorate the ordinary function
decorated_func = make_pretty(ordinary)

# call the decorated function
decorated_func()

输出

I got decorated
I am ordinary

在上面显示的示例中,make_pretty() 是一个装饰器。注意代码,

decorated_func = make_pretty(ordinary)
  • 我们现在将 ordinary() 函数作为参数传递给 make_pretty()
  • make_pretty() 函数返回内部函数,现在它被赋值给 decorated_func 变量。
decorated_func()

在这里,我们实际上调用了 inner() 函数,我们在其中打印

装饰器与 @ 符号

Python 提供了一种更优雅的方式来使用 @ 符号实现此功能,而不是将函数调用赋值给一个变量。例如:

def make_pretty(func):

    def inner():
        print("I got decorated")
        func()
    return inner

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

ordinary()  

输出

I got decorated
I am ordinary

在这里,ordinary() 函数使用 @make_pretty 语法被 make_pretty() 装饰器装饰,这等效于调用 ordinary = make_pretty(ordinary)


用参数装饰函数

上面的装饰器很简单,它只适用于没有任何参数的函数。如果我们的函数接受参数,例如:

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

这个函数有两个参数,ab。我们知道如果我们将 b 作为 0 传入,它将产生错误。

现在让我们创建一个装饰器来检查这种会导致错误的情况。

def smart_divide(func):
    def inner(a, b):
        print("I am going to divide", a, "and", b)
        if b == 0:
            print("Whoops! cannot divide")
            return

        return func(a, b)
    return inner

@smart_divide
def divide(a, b):
    print(a/b)

divide(2,5)

divide(2,0)

输出

I am going to divide 2 and 5
0.4
I am going to divide 2 and 0
Whoops! cannot divide

在这里,当我们使用参数 (2,5) 调用 divide() 函数时,会调用 smart_divide() 装饰器中定义的 inner() 函数。

inner() 函数使用参数 25 调用原始 divide() 函数并返回结果,即 0.4

类似地,当我们使用参数 (2,0) 调用 divide() 函数时,inner() 函数会检查 b 是否等于 0,并在返回 None 之前打印一条错误消息。


在 Python 中链式装饰器

在 Python 中可以链式使用多个装饰器。

要在 Python 中链式使用装饰器,我们可以通过将多个装饰器一个接一个地应用于单个函数,最里面的装饰器首先应用。

def star(func):
    def inner(*args, **kwargs):
        print("*" * 15)
        func(*args, **kwargs)
        print("*" * 15)
    return inner


def percent(func):
    def inner(*args, **kwargs):
        print("%" * 15)
        func(*args, **kwargs)
        print("%" * 15)
    return inner


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

printer("Hello")

输出

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

上述语法,

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

等同于

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

我们链式装饰器的顺序很重要。如果我们颠倒顺序,如下:

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

输出将是

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

另请阅读

视频:Python 中的 @ 装饰器

你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战