Flask钩子函数用法详解

本文详细介绍了Flask框架中的各种钩子函数,包括before_first_request、before_request、after_request、teardown_request等,展示了如何在不同阶段执行自定义逻辑,如权限验证、数据处理和错误处理。通过实例代码演示了如何灵活运用这些钩子提升代码复用和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、钩子函数介绍

        在Flask中钩子函数是使用特定的装饰器装饰的函数。

        为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码。那么这种函数就叫做钩子函数。

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。

2、before_first_request

        在处理项目第一个请求前执行。

@app.before_first_request    
def first_request():      
    print('first time request')

示例代码:

from flask import Flask


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    return 'Hello world!'


@app.before_first_request
def before_first_request():
    print("开始启动程序")


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

运行结果:

3、before_request

  • 在每次请求前执行。通常可以用这个装饰器来给视图函数增加一些变量。
  • 请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。
  • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用。
  • 一般这个就是在视图函数之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。
@app.before_request    
def before_request():      
    if not hasattr(g,'glo1'):          
        setattr(g,'glo1','想要设置的')

示例代码1:

from flask import Flask


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    return 'Hello world!'


@app.before_request
def before_request():
    print("开始执行API!")


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

运行结果:

示例代码2:

from flask import Flask, g, session


app = Flask(__name__)
app.config['SECRET_KEY'] = 'absdfbdssdf'


@app.route('/')
def index():
    print('执行index函数!')
    return 'Hello world!'


@app.route('/login/')
def login():
    print('开始登录')
    session['name'] = 'dgw'
    return '登录成功!'


@app.route('/home/')
def home():
    print('进入个人中心')
    if hasattr(g, 'name'):
        return f'用户已经登录,用户名为:{g.name}'
    else:
        return '用户没有登录!'


@app.before_request
def before_request():
    print("开始执行API!")
    name = session.get('name')
    if name:
        g.name = name
    print('这个是每次请求时,需要执行的逻辑!!!')


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

运行结果:

 

4、after_request

  • 如果没有抛出错误,在每次请求后执行
  • 接受一个参数:视图函数作出的响应
  • 在此函数中可以对响应值在返回之前做最后一步修改处理
  • 需要将参数中的响应在此参数中进行返回

示例代码:

from flask import Flask, g, session


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    return 'Hello world!'


# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
    print('本次接口执行完毕!')
    response.headers["Content-Type"] = "application/json"
    return response


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

运行结果:

5、teardown_request

  • 在每次请求后执行
  • 接受一个参数:错误信息,如果有相关错误抛出

示例代码:

from flask import Flask, g, session


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    return 'Hello world!'


# 每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
@app.teardown_request
def teardown_request(response):
    print("teardown_request")


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

运行结果:

6、teardown_appcontext

        不管是否有异常,注册的函数都会在每次请求之后执行。

@app.teardown_appcontext    
def teardown(exc=None):      
    if exc is None:        
        db.session.commit()      
    else:        
        db.session.rollback()      
        db.session.remove()

7、template_filter

        在使用Jinja2模板的时候自定义过滤器。

@app.template_filter("upper")  
def upper_filter(s):    
    return s.upper()

详见博文中自定义过滤器:Flask中Jinja2模板过滤器_flask jinja,内置过滤器 attr-优快云博客

8、context_processor

        上下文处理器。使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。

使用场景:

        这个钩子函数的功能是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的 render_template 中去写,这样可以让代码更加简洁和好维护。

@app.context_processor  
def context_processor():
    if hasattr(g,'user'):
        return {"current_user":g.user}
    else:
        return {}

示例代码:

from flask import Flask, request, g, session, current_app, render_template
import os


app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)


@app.route('/')
def index():
    print('执行index函数!')
    session['name'] = 'dgw'
    return render_template('index.html')


@app.route('/home/')
def home():
    print('进入个人中心')
    if hasattr(g, 'name'):
        print('name:', g.name)
    return render_template('index.html')


@app.before_request
def before_request():
    print('before_request!')
    name = session.get('name')
    if name:
        g.name = name


@app.context_processor
def context_processor():
    if hasattr(g, 'name'):
        return {'current_user': g.name}
    else:
        return {}


if __name__ == '__main__':
    app.run(debug=True)
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>这个是个人主页</h1>
    <p>当前用户:{{ current_user }}</p>
</body>
</html>

运行结果:

9、errorhandler

        errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用 errorhandler 来出来。

需要注意几点:

  • 在errorhandler装饰的钩子函数下,记得要返回相应的状态码。
  • 在errorhandler装饰的钩子函数中,必须要写一个参数,来接收错误的信息,如果没有参数,就会直接报错。
  • 使用 flask.abort 可以手动的抛出相应的错误,比如开发者在发现参数不正确的时候可以自己手动的抛出一个400错误。

示例代码1:

from flask import Flask, render_template


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    1 / 0
    return render_template('index.html')


@app.errorhandler(ZeroDivisionError)
def server_error(error):
    return render_template('500.html')


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

运行结果:

示例代码2:

from flask import Flask, render_template, abort


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    abort(500)
    return render_template('index.html')


@app.errorhandler(500)
def server_error(error):
    return render_template('500.html')


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

运行结果:

示例代码3: 【状态码虽然可以不写,但是推荐写上,这样可以告诉服务器是哪个错误】

from flask import Flask, render_template, abort


app = Flask(__name__)


@app.route('/')
def index():
    print('执行index函数!')
    abort(500)
    return render_template('index.html')


@app.errorhandler(500)
def server_error(error):
    return render_template('500.html'), 500  # 状态码虽然可以不写,但是推荐写上,这样可以告诉服务器是哪个错误


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

运行结果:

更多用法详见博文:Flask之异常处理_flask异常处理方法-优快云博客

10、综合案例使用

示例代码:

from flask import Flask

app = Flask(__name__)


# 在第一次请求之前调用,可以在此方法内部做一些初始化操作
@app.before_first_request
def before_first_request():
    print("before_first_request")


# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
    print("before_request")
    # if 请求不符合条件:
    #     return "laowang"


# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
    print("after_request")
    response.headers["Content-Type"] = "application/json"
    return response


# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
@app.teardown_request
def teardown_request(response):
    print("teardown_request")


@app.route('/')
def index():
    return 'index'


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

运行结果: 

  • 在第1次请求时的打印:
before_first_request
before_request
after_request
teardown_request
  • 在第2次请求时的打印:
before_request
after_request
teardown_request

``` from flask import Flask,render_template,request,redirect,url_for,session import unittest app=Flask(__name__) #主页 @app.route('/') def index(): if "username" in session: return redirect(url_for("success",name = session["username"])) return redirect(url_for("login")) #登录成功页面 @app.route('/success/<name>') def success(name): return f"{name} login success!" @app.route('/login',methods = ["GET","POST"]) def login(): if request.method == "POST": session["username"] = request.form.get("username") return redirect(url_for("success",name = session["username"])) return render_template('login1.html') if __name__ == '__main__': app.run(debug=True)```E:\202312100217-Ljw\flask_web\envs\python.exe "D:/Program Files/JetBrains/PyCharm 2023.1/plugins/python/helpers/pycharm/_jb_pytest_runner.py" --target get_post.py::test Testing started at 8:29 ... D:/Program Files/JetBrains/PyCharm 2023.1/plugins/python/helpers/pycharm/_jb_pytest_runner.py:8: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html from pkg_resources import iter_entry_points Launching pytest with arguments get_post.py::test --no-header --no-summary -q in E:\202312100217-Ljw\路由 ============================= test session starts ============================= collecting ... collected 1 item get_post.py::test FAILED [100%] get_post.py:3 (test) @app.route('/',methods = ['GET','POST']) def test(): > if request.method == 'GET': get_post.py:6: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ..\flask_web\envs\lib\site-packages\werkzeug\local.py:432: in __get__ obj = instance._get_current_object() ..\flask_web\envs\lib\site-packages\werkzeug\local.py:554: in _get_current_object return self.__local() # type: ignore _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ name = 'request' def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: > raise RuntimeError(_request_ctx_err_msg) E RuntimeError: Working outside of request context. E E This typically means that you attempted to use functionality that needed E an active HTTP request. Consult the documentation on testing for E information about how to avoid this problem. ..\flask_web\envs\lib\site-packages\flask\globals.py:33: RuntimeError ============================== 1 failed in 0.24s ============================== Process finished with exit code 1
03-20
从错误信息来看,`RuntimeError: Working outside of request context.` 表示你在测试代码中尝试访问 `request` 对象,但在当前环境中并没有激活的请求上下文。 ### 原因分析 Flask 的 `request` 和其他全局变量(如 `session`)依赖于请求上下文的存在。当你直接运行单元测试时,并未模拟实际的 HTTP 请求环境,因此会出现上述错误。 ### 解决方案 为了正确地对 Flask 应用程序进行单元测试,你需要创建一个虚拟的应用上下文并发送适当的请求数据到视图函数中。以下是修正后的代码: #### 修改后的测试代码: ```python import unittest from my_flask_app import app # 确保导入的是你的主应用模块 class TestFlaskApp(unittest.TestCase): def setUp(self): """初始化测试客户端""" self.app = app.test_client() def tearDown(self): pass # 如果需要清理资源可以在这里添加逻辑 def test_index_not_logged_in(self): """测试用户未登录时的情况""" response = self.app.get('/') # 模拟 GET 请求 self.assertEqual(response.status_code, 302) # 验证是否重定向 self.assertIn(b'/login', response.data) def test_login_and_redirect(self): """测试登录流程及跳转情况""" with app.test_client() as client: with client.session_transaction() as sess: sess['username'] = 'test_user' # 手动设置会话值 response = client.get('/') # 用户已登录,应该跳转至 success 页面 self.assertEqual(response.status_code, 302) self.assertIn(b'/success/test_user', response.data) if __name__ == '__main__': unittest.main() ``` --- ### 关键点说明: 1. **Test Client** 使用 `app.test_client()` 创建了一个测试客户端,用于向应用程序发出虚拟的 HTTP 请求。 2. **Session Simulation** 利用了 `client.session_transaction()` 来手动操作 session 数据,在某些场景下非常有用。 3. **Assertion Methods** 单元测试的核心部分在于验证返回的状态码 (`response.status_code`)、响应内容 (`response.data`) 是否符合预期。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值