大綱:

  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則是進行集中式處理,後續修改異常處理邏輯只需改一處

 

全局鈎子

  •   鈎子介紹

Flask介紹_html

 

Flask介紹_html_02

 

Flask介紹_局部變量_03

  

Flask介紹_解包_04

 

  •   鈎子用法

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}')

Flask介紹_局部變量_05

 

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

Flask介紹_局部變量_06

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... }}

常見內建過濾器

  字符串操作

Flask介紹_html_07

  列表操作

Flask介紹_html_08

語句塊過濾

{% 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

Flask介紹_html_09

#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()聲明