Flask 中的装饰器

总体来说,Python 的语法比较简单,是一门易学的编程语言。但是,Python 的装饰器语法是一个例外,装饰器是 Python 语言的高级语法,牵涉到高阶函数等内容,对初学者来说较难理解。

看到这里你可能会问:咱们这门课程不是学习 Flask 框架吗?怎么又扯到装饰器上面去了?
这是因为装饰器语法在 Flask 框架中得到了广泛的应用,想要学好 Flask 框架不理解装饰器的概念是不行滴。

因此这个小节我们就专门来讨论下装饰器的用途、定义和原理,为大家深入理解 Flask 框架原理打下一个重要基础。

1. 高阶函数

1.1 把函数当作对象

本节从底层原理讲解了如何实现装饰器,学员需要深入理解 Python 中 “函数是第一类对象” 的概念,可以参考词条 “Python 的 lambda 表达式”。在这个词条中对这一概念具有比较系统的讲解。

在 Python 中,函数是第一类对象的意思是指函数作为一个对象,与其它对象具有相同的地位。具体来说,一个数值对象可以:

  • 被赋值给变量;
  • 作为参数传递给函数;
  • 作为返回值。

因为函数和数值具有相同的地位,所以函数也可以:

  • 被赋值给变量;
  • 作为参数传递给函数;
  • 作为返回值。

1.2 把函数作为输入参数

上面说到函数也可以被作为参数传递给另外一个函数,下面我们就用一个例子来演示一下:

def double(item):
    return item + item

def triple(item):
    return item + item + item

定义函数 double,返回输入值的 2 倍;定义函数 triple,返回输入值的 3 倍。

def map(func, input):
    output = []
    for item in input:
        new_item = func(item)
        output.append(new_item)
    return output

定义函数 map,接受两个参数:func 和 input。参数 func 是一个函数,参数 input 是一个列表, 对输入列表 input 中的每个元素依次进行处理,返回一个新列表 output。

在第 3 行,遍历输入列表 input 中的每个元素,调用 func (item) 生成一个新的元素 new_item,将 new_item 加入到 output 中,最后返回 output。

print(map(double, [1, 2, 3]))
print(map(triple, [1, 2, 3]))

对序列 [1, 2, 3] 中的每个元素使用函数 double 进行处理;对序列 [1, 2, 3] 中的每个元素使用函数 triple 进行处理。

运行程序,输出如下:

[2, 4, 6]
[3, 6, 9]

序列 [1, 2, 3] 中的每个元素乘以 2 后,得到序列 [2, 4, 6];序列 [1, 2, 3] 中的每个元素乘以 3 后,得到序列 [3, 6, 9]。

1.3 把函数作为返回值

在下面的例子中,将函数作为返回值:

def func():
    print('Inside func')

def return_func():   
    print('Inside return_func')
    return func

在第 1 行,定义函数 func;在第 3 行,定义函数 return_func,函数 return_func 返回一个函数类型的对象,将函数 func 作为值返回。

var = return_func() 
var()

调用 return_func (),将函数的返回值保存到变量 var。变量 var 的类型是函数,因此可以进行函数调用。

程序的输出结果如下:

Inside return_func
Inside func

2. 装饰器与高阶函数

在上面的两个例子中把函数作为参数或者吧函数作为返回值的函数在 Python 中被统称为是高阶函数,而我们本节课的重点装饰器本质上其实就是一个特殊的高阶函数,那么它特殊在哪里呢?

通过上面的两个例子我们可以了解到高阶函数有两个特性:

  • 输入参数是函数;
  • 输出返回值是函数。

而装饰器(decorate)则是两种特性都具备,也就是说装饰器函数的参数是一个函数,返回值也是一个函数

图片描述

函数 decorate 对函数 input 的功能进行扩充,生成并返回一个新的函数 output,新的函数 output 的功能基于函数 input。装饰器的中装饰的含义是指:对函数 input 的功能进行装饰 (扩充功能),得到一个新函数 output

3. 装饰器的用途

既然我们已经知道了装饰器本质上就是一个函数,只不过比较特殊而已,下面我们就一起来看下装饰器可以用在什么地方:

3.1 需求

使用 Python 编写了 3 种排序算法:

  • quick_sort,快速排序;
  • bubble_sort,冒泡排序;
  • select_sort,选择排序。

现在需要对这 3 个算法进行性能评测,记录并打印每个排序算法的执行时间。

import time

def quick_sort():
    time.sleep(1)

def bubble_sort():
    time.sleep(2)

def select_sort():
    time.sleep(3)

引入 time 模块,使用 time.sleep () 模拟函数的执行时间;使用 time.sleep (1) 模拟 quick_sort 的执行时间为 1 秒;使用 time.sleep (2) 模拟 bubble_sort 的执行时间为 2 秒;使用 time.sleep (3) 模拟 select_sort 的执行时间为 3 秒。

3.2 不使用装饰器

对于这个需求,我们先不使用装饰器,仅使用 Python 的基础语法完成任务,如下所示:

import time

def quick_sort():
    start_time = time.time()
    time.sleep(1)
    end_time = time.time()
    print('%.2f seconds' % (end_time - start_time))

引入 time 模块,需要使用 sleep 方法;在函数的头部,记录开始时间 start_time; 在函数的尾部,记录结束时间 end_time;打印开始时间和结束时间的差,即函数的执行时间。

def bubble_sort():
    start_time = time.time()
    time.sleep(2)
    end_time = time.time()
    print('%.2f seconds' % (end_time - start_time))

def select_sort():
    start_time = time.time()
    time.sleep(3)
    end_time = time.time()
    print('%.2f seconds' % (end_time - start_time))

使用同样的方法,对 bubble_sort 和 select_sort 进行修改。

quick_sort()
bubble_sort()
select_sort()

依次调用 quick_sort、bubble_sort、select_sort,打印它们各自的运行时间,程序输出如下:

1.00 seconds
2.00 seconds
3.00 seconds

3.3 使用装饰器

在上一个小节中,我们没有使用装饰器同样完成了需求,但是这样做存在一个明显的问题:

  • quick_sort、bubble_sort、select_sort 存在代码重复;
  • 在函数头部记录开始时间、在函数尾部记录结束时间,逻辑是完全相同的。

而通过使用装饰器,可以消除代码重复,代码如下:

import time

def quick_sort():
    time.sleep(1)

def bubble_sort():
    time.sleep(2)

def select_sort():
    time.sleep(3)

在上一节的例子中,需要对 quick_sort、bubble_sort 和 select_sort 进行修改。在本节的例子中,不对 quick_sort、bubble_sort 和 select_sort 进行任何修改。

def decorate(input_sort):
    def output_sort():
        start_time = time.time()
        input_sort()
        end_time = time.time()
        print('%.2f seconds' % (end_time - start_time))

    return output_sort

装饰器 decorate 是一个高阶函数,输入参数 input_sort 是一个排序函数,返回值是 output_sort 一个功能增强的排序函数。

在第 3 行,在 output_sort 函数的头部,记录开始时间,调用原排序函数 input_sort;在第 5 行,在 output_sort 函数的尾部,记录结束时间。

quick_sort = decorate(quick_sort)
bubble_sort = decorate(bubble_sort)
select_sort = decorate(select_sort)

使用 decorate (quick_sort),生成一个功能增强的 quick_sort,并替换原有的 quick_sort;使用 decorate (bubble_sort),生成一个功能增强的 bubble_sort,并替换原有的 bubble_sort;使用 decorate (select_sort),生成一个功能增强的 select_sort,并替换原有的 select_sort。

quick_sort()
bubble_sort()
select_sort()

依次调用 quick_sort、bubble_sort、select_sort,打印它们各自的运行时间,程序输出如下:

1.00 seconds
2.00 seconds
3.00 seconds

4. Python 的装饰器语法

4.1 装饰器语法

对装饰器这样的高阶函数的应用,Python 提供了特殊的装饰器语法,用法如下:

def decorate(input_function):
    def output_function():
        pass
    return output_function

@decorate
def input_function():
    pass

首先定义装饰器函数 decorate,然后使用 @decorate 装饰需要增强功能的函数 input_function。以上使用装饰器语法 @decorate 的代码会被翻译如下:

def decorate(input_function):
    def output_function():
        pass
    return output_function

def input_function():
    pass

input_function = decorate(input_function)

decorate 函数接受输入参数 input_function,返回一个功能增强的函数 output_function。 用功能增强的新函数 output_function 替换原有的旧函数 input_function。

4.2 使用装饰器语法

在本小节,使用 Python 的装饰器语法实现对三种排序算法的性能评测:

import time

def decorate(input_sort):
    def output_sort():
        start_time = time.time()
        input_sort()
        end_time = time.time()
        print('%.2f seconds' % (end_time - start_time))

    return output_sort

装饰器 decorate 是一个高阶函数,输入参数 input_sort 是一个排序函数,返回值是 output_sort 一个功能增强的排序函数。

在 output_sort 函数的头部,记录开始时间,调用原排序函数 input_sort,在 output_sort 函数的尾部,记录结束时间。

@decorate
def quick_sort():
    time.sleep(1)

@decorate
def bubble_sort():
    time.sleep(2)

@decorate
def select_sort():
    time.sleep(3)

使用装饰器 decorate 装饰 quick_sort,得到一个功能增强的 quick_sort;使用装饰器 decorate 装饰 bubble_sort,得到一个功能增强的 bubble_sort;使用装饰器 decorate 装饰 select_sort,得到一个功能增强的 select_sort。

quick_sort()
bubble_sort()
select_sort()

依次调用 quick_sort、bubble_sort、select_sort,打印它们各自的运行时间,程序输出如下:

1.00 seconds
2.00 seconds
3.00 seconds

5. 小结

本小节讲解了装饰器的原理以及装饰器的功能,装饰器主要用于在原有函数的基础上增强功能,使用思维导图概括如下:

图片描述