Flask 的钩子函数使用

Flask 框架提供了钩子函数的机制用于在特定的位置执行用户注册的函数,本小节讲解了常见的钩子函数的使用。

1. 钩子简介

1.1 什么是钩子

在服务端处理请求时,有时需要:在处理请求前执行一些准备工作,在处理请求后执行一些扫尾工作,例如:

@app.route('/page1')
def page1():
    执行准备工作
    执行请求
    执行扫尾工作

@app.route('/page2')
def page2():
    执行准备工作
    执行请求
    执行扫尾工作

程序包括有 2 个页面:page1 和 page2,在每个页面处理函数的头部,执行准备工作;在每个页面处理函数的尾部,执行扫尾工作。

以上程序存在的明显的代码重复问题,为了让每个页面处理函数避免编写重复功能的代码,Flask 提供了钩子函数机制:Flask 给应用程序提供挂载点,在挂载点调用应用程序注册的函数,该函数被称为钩子函数。

1.2 注册钩子

Flask 框架处理请求时,提供了 2 个挂载点:before_request 和 after_request,执行请求的流程如下:

  1. 执行在挂载点 before_request 注册的钩子函数;
  2. 执行页面处理函数;
  3. 执行在挂载点 after_request 注册的钩子函数。

Flask 使用装饰器的语法注册钩子函数,如下所示:

@app.before_request
def before_request():
    print('before request')

使用装饰器 @app.before_request 在挂载点 before_request 注册函数 before_request,Flask 在处理每个请求时:

  • 首先调用函数 before_request,打印字符串 ‘before request’;
  • 然后再执行请求的处理函数。

1.3 消除代码重复

下面使用钩子函数机制消除 1.1 小节的代码重复:

@app.before_request
def before_request():
    执行准备工作

@app.after_request
def after_request():
    执行扫尾工作

@app.route('/page1')
def page1():
    执行请求

@app.route('/page2')
def page2():
    执行请求

在第 1 行,在挂载点 before_request 注册处理函数,执行请求前会调用该函数执行准备工作;在第 5 行,在挂载点 after_request 注册处理函数,执行请求前后调用该函数执行扫尾工作。

与 1.1 小节中的代码相比,页面 /page1 和页面 /page2 的处理函数得到了简化,准备工作和扫尾工作被统一放置在钩子函数中进行。

2. 常用的钩子

Flask 框架中常用的钩子总结如下:

1. before_first_request
在应用程序实例的第一个请求之前要运行的函数,只会运行一次。

2. before_request
在每个请求之前要运行的函数,对每一次请求都会执行一次。

3. after_request
在每个请求之后要运行的函数,对每一次请求都会执行一次。

在 after_request 注册的钩子函数的第一个参数是 response 对象。

4. teardown_request
在每个请求之后要运行的函数,对每一次请求都会执行一次。

在 teardown_request 注册的钩子函数的第一个参数是 exception 对象,指向执行请求时发生的错误。

5. errorhandler
发生一些异常时,比如 HTTP 404、HTTP 500 错误,或者抛出异常,就会自动调用该钩子函数。

3. 钩子的基本使用

3.1 程序代码 basis.py

编写一个程序 basis.py 分别在处理请求前、处理请求后注册钩子函数:

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def index():
    print('execute request')
    return 'Hello World'

@app.before_first_request
def before_first_request():
    print('before_first_request')

@app.before_request
def before_request():
    print('before_request')

@app.after_request
def after_request(response):
    print('after_request')
    return response

if __name__ == '__main__':
    app.run()

在第 10 行,通过 @app.before_first_request 注册在第一次处理请求时执行的钩子函数,该函数打印字符串 ‘before_first_request’。

在第 14 行,通过 @app.before_request 注册在每次处理请求前执行的钩子函数,该函数打印字符串 ‘before_request’。

在第 18 行,通过 @app.after_request 注册在每次处理请求后执行的钩子函数,该函数打印字符串 ‘after_request’。钩子函数 after_request(response) 的输入参数是一个 Response 对象,函数返回的也是一个 Response 对象。

3.2 运行程序 basis.py

1. 启动程序

$ python basis.py

2. 在浏览器中首次访问 http://localhost:5000

3. 观察控制台的输出

before_first_request
before_request
execute request
after_request

应用程序在第一个请求之前要运行一次 before_first_request,只会运行一次。

4. 在浏览器中再次访问 http://localhost:5000

5. 观察控制台的输出

before_request
execute request
after_request

在程序整个生命周期内,before_first_request,只会运行一次,因此再次访问网站时,不会再执行 before_first_request。

4. after_request 与 teardown_request 的区别

4.1 程序代码 diff.py

本节通过一个具体的例子讲解 after_request 与 teardown_request 的区别,代码如下:

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def index():
    print('execute request without error')
    return 'Hello World'

@app.route('/error')
def error():
    print('execute request with error')
    1 / 0
    return 'Hello World'

@app.after_request
def after_request(response):
    print('after_request')
    return response

@app.teardown_request
def teardown_request(exception):
    print('teardown_request:', exception)

if __name__ == '__main__':
    app.run()

在第 5 行,访问页面 / 时执行函数 index(),该函数打印字符串 ‘execute request without error’,执行期间没有发生异常。

在第 10 行,访问页面 /error 时执行函数 error(),该函数打印字符串 ‘execute request with error’,执行期间发生异常。在第 13 行,人为的通过 1 / 0 引发除零异常。

在第 16 行,注册 after_request 钩子函数,执行请求后会调用该钩子函数。

在第 21 行,注册 teardown_request 钩子函数,执行请求后都会调用该钩子函数。钩子函数的第一个参数是 exception 对象,指向执行请求时发生的错误。

4.2 运行程序 diff.py

1. 启动程序 diff.py

$ python diff.py

2. 在浏览器中访问 http://localhost:5000

3. 观察控制台的输出

execute request without error
after_request
teardown_request: None

访问页面 / 时,执行处理函数 index,执行请求结束后,会调用 after_request 和 teardown_request。该函数执行期间没有发生异常,传递给 teardown_request 钩子函数的 exception 对象为 None。

4. 在浏览器中访问 http://localhost:5000/error

5. 观察控制台的输出

execute request with error
Traceback (most recent call last):
  File "diff.py", line 14, in error
    1 / 0
ZeroDivisionError: division by zero
after_request
teardown_request: division by zero

访问页面 /error 时,执行处理函数 error,执行请求结束后,会调用 after_request 和 teardown_request。该函数执行期间发生异常,传递给 teardown_request 钩子函数的 exception 对象为 ZeroDivisionError。

5. 错误处理

5.1 程序代码 error.py

通过一个具体的例子讲解使用 errorhandler 处理 HTTP 错误,代码如下:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    print('execute request')
    return 'Hello World'

@app.errorhandler(404)
def errorhandler(e):
    print('error_handler(404)')
    print(e)
    return '404 Error'

if __name__ == '__main__':
    app.run()

在第 10 行,使用 @errorhandler(404) 注册一个钩子函数 errorhandler(e),当发生 HTTP 404 错误(页面不存在)时,调用钩子函数,该函数的第一个参数是错误对象。

5.2 运行程序 error.py

1. 启动程序 error.py

$ python error.py

2. 在浏览器中访问 http://localhost:5000/non-exist-page

3. 观察浏览器的显示

404 Error

访问一个不存在的页面 /non-exist-page,产生 HTTP 404 错误,Flask 框架执行使用 @errorhandler(404) 注册的函数 errorhandler,该函数返回字符串 ‘404 Error’ 给浏览器作为响应。

5. 观察控制台的输出

error_handler(404)
404 Not Found: The requested URL was not found on the server. If
 URL manually please check your spelling and try again.

执行 errorhandler 时,首先打印字符串 ‘error_handler(404)’,然后打印 Error 对象。

6. 源代码下载

7. 小结

本小节讲解了钩子函数的语法和常见的钩子函数,对本小节讲解的重点,使用思维导图概括如下:

图片描述