web框架——flask-异常处理/全局钩子/jinja2引擎

    大纲:

  1.异常处理

    主动抛出HTTP异常和业务异常  及  两者使用场景

    捕获异常 @app.errorhandler装饰器及  与  except区别

  2.全局钩子

    钩子介绍

    钩子用法

  3.jinja2引擎

    变量的传入  /  输出

    流程控制  /  仅在for循环中可使用的jinja2模板专属内置变量loop

    过滤器

      内建过滤器

      自定义过滤器

    模板继承

 

 

 

异常处理

  •   主动抛出HTTP异常和业务异常及两者使用场景

主动抛出HTTP异常

from flask import Flask,abort

  abort主动抛出指定的异常状态码,且只能抛出HTTP协议的错误状态码

  abort参数:第一个参数为状态码,后续参数表示错误相关的提示内容

主动抛出业务异常

  raise

  相关回顾 https://www.doubao.com/thread/w48582e0fb73cbf7d

       https://www.cnblogs.com/guohan222/p/19144544

两者使用场景区别

  abort专门处理HTTP标准错误,需要传入状态码 如 abort(404)

  raise专门处理业务异常

 

  •   捕获异常 @app.errorhandler装饰器及与except区别

@app.errorhandler装饰器进行异常捕获

    注册一个错误处理程序,当程序出现异常时被对应的错误处理程序捕获,并且调用对应错误处理程序装饰的方法

    参数:HTTP的错误状态码  /  raise的指定异常

  捕获HTTP的错误状态码

from flask import Flask, request,abort
app = Flask(__name__)
@app.route('/index', methods=['GET'])
def index():
    if not request.args.get('pwd'):
        abort(404,'无资源')
    return 'ok'

@app.errorhandler(404)              #此处不仅可以捕获abort抛出的HTTP异常,也可以捕获系统抛出的
def NotInfo(exc):
    print(exc.code)                 #404
    print(exc.description)          #无资源
    return f'{exc}'                 #404 Not Found: 无资源
if __name__ == '__main__':
    app.run(debug=True)

  捕获raise抛出的指定异常

from flask import Flask, request,abort
app = Flask(__name__)

#自定义异常
class MoneyError(Exception):
    def __init__(self,message,code=400):
        self.message = message
        self.code = code

@app.route('/index', methods=['GET'])
def index():
    if not request.args.get('money'):
        raise MoneyError('余额不足无法支付!')
    return '支付成功!'

#注册对应异常的异常处理程序
@app.errorhandler(MoneyError)
def MyErrorHandler(exc):
    print(exc.message)
    print(exc.code)
    return f'{exc}'                 #余额不足无法支付!

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

与except区别

except捕获局部代码块内抛出的异常

  若用except处理所有异常则要在每个路由函数,工具函数中重复写捕获

app.errorhandler装饰器捕获整个请求处理流程中的异常,是Flask为Web场景设置的机制

  若用@app.errorhandler则是进行集中式处理,后续修改异常处理逻辑只需改一处

 

全局钩子

  •   钩子介绍

image image image  image

 

  •   钩子用法

def before_first_request():
    """
    此钩子只在项目启动后,第一次请求到达时执行
    用于初始化全局资源(如数据库连接池,加载配置缓存等)
    :return:
    """
    pass
app.before_first_request_funcs.append(before_first_request)
@app.before_request
def before_request():
    """
    此钩子在每一次请求处理前执行
    常用于用户登录状态校验,添加请求日志等
    :return:
    """
    pass
@app.after_request
def after_request(response):
    """
    在每一次请求处理完成后(无论视图函数是否异常)执行
    需要接收response对象并且返回该对象
    常用于统一设置响应头,修改响应内容,记录响应耗时等
    :param response:
    :return:
    """
    return response
@app.teardown_request
def teardown_request(exc):
    """
    在每一次请求处理结束后执行(无论是否有异常均执行)
    需要接收exception参数(无异常时为None)
    常用于释放资源,记录异常日志等
    :param exc:
    :return:
    """
    print(f'错误信息:{exc}')

image

 

jinja2引擎

  •   变量的传入/输出

传入变量

from flask import Flask, render_template
render_template('login.html',**locals())
关于**locals()与locals()
知识困惑点:python基础中序列解包知识点模糊
**locals()
    **locals() 是 Python 的字典解包语法,locals() 会以字典形式返回当前函数内的所有局部变量(比如你定义的 info_list等),解包后会变成 key=value 的形式传给 render_template;
    其是将当前函数的所有局部变量打包传入模板,而不能将非此函数内的变量(全局变量)打包传入模板
    locals() 会把当前函数内所有局部变量都传入模板(包括一些你可能不需要的临时变量),虽然方便,但不够灵活。
    如果只想传入特定变量,更推荐显式指定(比如 render_template('login.html', info_list=info_list, user=user)),避免模板中出现冗余变量。

python内置函数locals()
知识点弥补:locals() 将当前作用域(比如 Flask 视图函数内)的所有局部变量组成字典
    @app.route('/book')
    def book():
    info_list = [{'id':1, 'bookname':'Python'}]  # 局部变量1
    title = '图书列表'  # 局部变量2
    print(locals())  # 输出:{'info_list': [...], 'title': '图书列表', 'self': ...}(包含所有局部变量)
    return render_template('book.html', **locals())
知识点弥补:  render_template()的相关参数
之前错误写法: render_template('login.html'),**locals()
修正:       render_template('login.html',**locals())
原因:       错误写法中逗号会拆分代码,**locals() 单独存在是语法错误;就算去掉逗号,**locals() 没作为 render_template 的参数传入,模板也拿不到数据;
            正确写法中,**locals() 是作为 render_template 的关键字参数解包,会把当前函数的所有局部变量打包传入模板,模板才能通过 {{ 变量名 }} 访问。
            若不解包直接传字典(如 locals()),模板无法识别字典内的键值对,只能拿到整个字典对象

 输出变量(传入的变量/内置的全局变量如request.path)

{#
py文件下
    name = 'gh'
    let_set = {1,2,3,(111,222)}
    let_dict = {'name':'gh','info':[1,2,3]}
#}
<p>name:{{ name }}</p>
<p>{{ let_set }}</p>
<p>{{ let_dict.get('name') }}</p>
<p>{{ let_dict.name }}</p>
<p>{{ let_dict.get('info').0 }}</p>        #点语法不能支持负索引
<p>{{ let_dict.get('info')[-1] }}</p>    

 

  •   流程控制

条件判断

{% if 条件 %}
    {# 业务逻辑 #}
{% elif 条件%}
    {# 业务逻辑 #}
{% else %}
    {# 业务逻辑 #}
{% endif %}

循环

{% for item in let_dict %}
    {# 业务逻辑 #}
{% endfor %}
{% for item in let_dict %}
    {# 业务逻辑 #}
{% else %}
    {# 业务逻辑 #}
{% endfor %}

jinja2模板引擎专属内置变量loop

  仅在for循环中可使用的jinja2模板专属内置变量loop

image

jinja2变量输出与流程控制案例
案例:生成表格

目录结构
-web文件夹
    -day4
        -main.py
    -templates
        -login.html
        
templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ info_list.0 }}</title>
    <style>
        .table {
            border: 3px solid red;
            border-collapse: collapse;
            width: 800px;
        }

        .table tr th,
        .table tr td {
            border: 1px solid red;
            text-align: center;
        }
    </style>
</head>
<body>
<table class="table">
    <tr>
        <th>次序</th>
        <th>id</th>
        <th>书名</th>
        <th>描述</th>
    </tr>
    {% for item in info_dict %}
        <tr style="background-color: {% if loop.first %} red {% else %} blue {% endif %}">
            <td>{{ loop.index }}</td>
            <td>{{ item.get('id') }}</td>
            <td>{{ item.get('bookname') }}</td>
            <td>{{ item.get('username') }}</td>
            <td>{{ loop.cycle('男','女') }}</td>
        </tr>
    {% else %}
        <!-- 当info_list为空时,显示空提示 -->
        <tr>
            <td colspan="4" style="background-color: #f5f5f5;">暂无图书数据</td>
        </tr>
    {% endfor %}
</table>
</body>
</html>



main.py

from flask import Flask, render_template
import os

root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
template_path = os.path.join(root_dir, 'templates')
app = Flask(__name__, template_folder=template_path)

#app = Flask(__name__,template_folder='../templates')

app.config.from_object('config.DevelopmentConfig')


@app.route('/login/', methods=['GET', 'POST'])
def login():
    info_list = ['界面', '标题']
    info_dict = [
        {'id': 22, 'bookname': '士大夫', 'username': 1},
        {'id': 33, 'bookname': '撒地方', 'username': 2},
        {'id': 44, 'bookname': '啊书法大赛', 'username': 3}
    ]

    return render_template('login.html',**locals())


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




{#
错误总结

python相关  flask框架中局部变量传入模板,序列解包,内置函数locals
1. main文件里面变量传入到模板里面时
   知识点弥补:  render_template()的相关参数
   之前错误写法: render_template('login.html'),**locals()
   修正:       render_template('login.html',**locals())
   原因:       错误写法中逗号会拆分代码,**locals() 单独存在是语法错误;就算去掉逗号,**locals() 没作为 render_template 的参数传入,模板也拿不到数据;
               正确写法中,**locals() 是作为 render_template 的关键字参数解包,会把当前函数的所有局部变量打包传入模板,模板才能通过 {{ 变量名 }} 访问。
               若不解包直接传字典(如 locals()),模板无法识别字典内的键值对,只能拿到整个字典对象

2.关于**locals()与locals()
知识困惑点:python基础中序列解包知识点模糊
**locals()
    **locals() 是 Python 的字典解包语法,locals() 会返回当前函数内的所有局部变量(比如你定义的 info_list等),解包后会变成 key=value 的形式传给 render_template;
    其是将当前函数的所有局部变量打包传入模板,而不能将非此函数内的变量(全局变量)打包传入模板
    locals() 会把当前函数内所有局部变量都传入模板(包括一些你可能不需要的临时变量),虽然方便,但不够灵活。
    如果只想传入特定变量,更推荐显式指定(比如 render_template('login.html', info_list=info_list, user=user)),避免模板中出现冗余变量。

python内置函数locals()
知识点弥补:locals() 将当前作用域(比如 Flask 视图函数内)的 所有局部变量组成的字典
    @app.route('/book')
    def book():
    info_list = [{'id':1, 'bookname':'Python'}]  # 局部变量1
    title = '图书列表'  # 局部变量2
    print(locals())  # 输出:{'info_list': [...], 'title': '图书列表', 'self': ...}(包含所有局部变量)
    return render_template('book.html', **locals())


前端相关
知识点不足:  给边框加粗细与颜色:         border:xx px solid color;
            后代选择器:
            给表格内部加边框:
            表格内部边框进行重合:       border-collapse: collapse;
            文本水平居中:             text-align:center
            文本垂直居中:              line-height:height
            表格的合并:              <td colspan="4" style="background-color: #f5f5f5;">暂无图书数据</td>

#}

 

  •   过滤器(本质就是函数)

使用格式

{{ 代码段/变量|过滤器1|过滤器2... }}

常见内建过滤器

  字符串操作

image

  列表操作

image

语句块过滤

{% filter upper %}
    <p>a</p>
    <p>b</p>
    <p>c</p>
{% endfilter %}

自定义过滤器

  方式一                  @app.template_filter('自定义过滤器名字') 

#注册过滤器方式一

#注册保留2位小数的过滤器
@app.template_filter('fixed2')
def do_fixed2(data):
    return f'{data:.2f}'


#模板中使用
{{ num|fixed2 }}

  方式二(推荐,自己写filter.py配置文件)   app.add_template_filter(value,key)     模仿 Lib\site-packages\jinja2\filters.py

image

#filter.py文件

def do_fixed2(data):
    return f'{data:.2f}'

FILTERS={
    'fixed2':do_fixed2
}


#main.py文件

from flask import Flask, request, abort, render_template
    #将配置文件里面的FILTERS引入main.py文件里
from filters import FILTERS
app = Flask(__name__,template_folder='../templates')
    #批量注册自定义过滤器到当前实例对象
for key,value in FILTERS.items():
    app.add_template_filter(value,key)
    
    
#模板使用过滤器
{{ num|fixed2 }}
    
    
即
#将配置文件里面的FILTERS引入main.py文件里
from filters import FILTERS
#批量注册自定义过滤器到当前实例对象
for key,value in FILTERS.items():
    app.add_template_filter(value,key)

 

  •   模板继承

父模板(base.html)

  预留区代码块格式(block标签定义可重写的内容范围)

{% block 区代码块名称 %}xxx{% endblock 区代码块名称 %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{% block title %}base标题{% endblock %}</title>
</head>
<body>
{% block menu %}首页-关于-联系我们{% endblock %}
{% block goods %}
    <p>会话</p>
{% endblock %}
</body>
</html>

子模版

  继承父模板格式

{% extends "base.html" %}

  填充父模板中预留的区代码快

{% extends "base.html" %}
{% block title %}
    index2标题
{% endblock title %}

{% block menu %}
    {{ super() }}
    -你好
{% endblock menu %}

{% block goods %}
    <p>牛奶</p>
    <p>牛肉</p>
    <p>豆浆</p>
{% endblock goods %}

{#  super()继承父模板原有内容,可在父模板menu快原内容基础上新增内容#}
{#  不写则是在子模版中重新定义此menu块#}

注意点

  1. 一个项目中可以有多个父模板但是一个子模版只能继承一个父模板
  2. 若在子模版中不想覆盖父模板某区代码块原有内容并在其原内容上新增内容则使用super()声明

 

posted @ 2025-11-26 22:08  guohan  阅读(11)  评论(0)    收藏  举报