前面已經提到過,後端回應前端的方式包含字串、JSON、重新導向,而最後一個則是使用樣板(或稱為模板)。樣板引擎是一種把 Python 資料變成 HTML 頁面的工具。
Flask 預設使用的樣板引擎叫 Jinja2,可以讓我們:
- 將變數嵌入 HTML 裡
- 加入控制語句(if、for)
- 抽出重複的區塊(如頁首、頁尾)
- 讓程式與畫面分離
而樣板的檔案形式可以有很多種,從純文字到 HTML 檔皆可作為樣板。至於為何要使用樣板,從名字便可知一二:樣板提供網頁設計者僅針對需要動態修改的地方做更動,其他重複的內容一律都是樣板。
渲染樣板
建立樣板的方式很簡單,僅需在 app.py
同層(根目錄)中建立 templates
資料夾,將樣板檔案放在裡面即可完成前置作業。接著,需要在 Python 前面從 Flask 引入 render_template
函數:
1 | from flask import render_template |
接著即可開始使用,語法為:
1 |
|
純文字處理
我們在 templates
中新增一個純文字檔 greet.txt
:
1 | 您好,歡迎光臨 |
在 /greet
路徑如下使用:
1 |
|
執行後打開網站進入 http://127.0.0.1:5000/greet
即可看到結果:

圖 1:使用樣板渲染
傳入資料
為了傳入資料至樣板中,我們使用佔位符號告訴 Jinja2 這塊是需要特別處理的:
1 | {{ 變數名 }} |
例如我們在 templates
新增 greet.html
這份檔案,然後撰寫以下內容:
1 | <html lang="en"> |
其中 {{ name }}
可以允許程式碼將變數傳入:
1 |
|
執行後即可得到以下結果:

圖 2:傳入變數至樣板
樣板繼承
在一個網站中,許多網頁會有相同的區塊,例如頁首、導覽列、頁尾等。若每個樣板都要重複寫這些內容,不但麻煩也容易出錯。為了解決這個問題,Flask 的 Jinja2 提供樣板繼承機制,只需定義一次這些「框架」,其餘頁面可以繼承這個樣板並只填入各自不同的部分。簡單來說,就是建立一份「父樣板」(如 base.html
),再讓其他樣板(如 index.html
)繼承這份父樣板並改寫指定區塊。
建立父樣板
在 templates
資料夾中建立 base.html
,作為整個網站的框架:
1 |
|
建立子樣板
接著,我們建立一個子樣板 index.html
,用來繼承 base.html
並改寫部分內容:
1 | {% extends "base.html" %} |
這裡使用:
{% extends "base.html" %}
:表示這份樣板會繼承父樣板{% block title %}
和{% block content %}
:覆寫父樣板對應的區塊
搭配 Python 使用
在 app.py
中對應的路由會寫成:
1 |
|

圖 3:樣板繼承
打開網站首頁時,Flask 會自動渲染 index.html
,再由 Jinja2 把它合併進 base.html
的框架裡,最後變成一個完整的 HTML 頁面。因此整體架構可以表示如下:
1 | templates/ |
此外請注意:
- 所有
{% block xxx %}
的名稱必須與父樣板相同,才能成功覆寫 - 如果子樣板中沒有覆寫某個區塊,就會保留父樣板的預設內容
控制結構
在樣板中,我們經常需要根據條件顯示特定內容,或根據資料重複顯示一段 HTML。Jinja2 提供了常見的控制語法,包括 if
條件語句與 for
迴圈語句。這些語法都以 {% ... %}
包裹,不能用 {{ ... }}
,因為它們不會顯示值,而是負責控制流程。
if 條件語句
我們可以根據變數是否存在、是否為特定值,來決定是否顯示某些區塊:
1 | {% if user %} |
在 Python 中傳入 user
變數:
1 |
|
執行後會顯示:

圖 4:控制語句-if (1)
若改為 user=None
,則會顯示:

圖 5:控制語句-if (2)
for 迴圈語句
當你有一個清單時,可以使用 for
來一一列出清單中的內容。例如在樣板中列出購物車的商品:
1 | <h2>購物車內容</h2> |
對應的路由程式碼如下:
1 |
|
畫面顯示為:

圖 6:控制語句-for (1)
也可以使用 loop.index
或 loop.last
等內建變數來取得當前迴圈資訊:
1 | <h2>購物車內容</h2> |
畫面就會顯示當前 item
的索引與值:

圖 7:控制語句-for (2)
過濾器
在樣板中,我們有時希望對變數做一些小修改再顯示,例如:將字母變大寫、將數字四捨五入。Jinja2 提供了許多**過濾器(filter)**來做這類處理,語法為:
1 | {{ 變數名 | 過濾器名稱 }} |
這裡的 |
稱為管線符號 (pipe),意思是把變數送進這個過濾器裡做處理。
過濾器名稱 | 功能說明 | 範例 |
---|---|---|
capitalize |
首字母大寫 | {{ name | capitalize }} |
upper |
全部轉大寫 | {{ name | upper }} |
lower |
全部轉小寫 | {{ name | lower }} |
title |
每個單字首字母大寫 | {{ name | title }} |
length |
回傳長度(字數、項目數) | {{ items | length }} |
round(2) |
四捨五入到小數點後 2 位 | {{ price | round(2) }} |
單一過濾器
我們可以格式化使用者輸入:
1 | <h1>歡迎,{{ name | capitalize }}</h1> |
對應的路由寫法為:
1 |
|
結果顯示:

圖 8:單一過濾器
多重過濾器
如果需要將變數進行多重處理,可以連用多個過濾器,例如先變大寫再加上長度:
1 | {{ name | upper | length }} |
這樣就會將 name
全部轉為大寫後,取得字串長度。
自訂過濾器
除了使用 Jinja2 內建的過濾器外,我們也可以根據需求自己定義過濾器函式,並註冊到樣板引擎中。這個功能特別適合處理有客製化格式的輸出,例如文字轉換、特殊字串處理、資料替換等。在 Flask 中,自訂過濾器的流程包含兩個步驟:
- 撰寫 Python 處理邏輯(過濾器函式)
- 將它註冊到 Jinja2 樣板環境
例如我們想要實作反轉字串,讓樣板能夠使用 reverse
過濾器,把輸入的文字顛倒顯示。例如 hello
變成 olleh
。首先在 Python 中定義這個函式:
1 | def reverse_string(s): |
接著註冊這個函式為一個過濾器名稱:
1 | app.jinja_env.filters["reverse"] = reverse_string |
這樣之後在任何樣板中就可以使用 | reverse
:
1 | <p>{{ name | reverse }}</p> |
例如我們可以在 /reverse
這個路徑中傳入 name
將其反轉:
1 |
|
執行後畫面就會顯示反轉後的結果 ynohtnA
:

圖 9:自訂過濾器
樣板復用
當我們的網站越做越大時,樣板的結構也會跟著變得複雜。如果每個頁面都手動重複寫 HTML 區塊(像按鈕、表格、卡片),不但麻煩,也容易出錯。
這時,我們可以利用:
- 樣板引用 (include):把一段樣板拆成獨立檔案,在其他樣板中「插進來」
- 樣板函式 (marcro):定義可以重複使用的「樣板函式」,就像 Python 的函式一樣
這兩種工具能讓我們的樣板更模組化、乾淨、好維護。
樣板引用
例如我們可以把一段 HTML 存成獨立檔案(例如 header.html
),然後在其他樣板中這樣插入:
1 | {% include "模板名稱.html" %} |
舉例來說,假設我們已經寫好頁首與頁尾的 HTML 檔案,只要在其他 HTML 檔案中套用上面的語法即可:
1 | <body> |
日後要改頁首與頁尾,只需要改一個檔案,全部樣板都會更新。
樣板函式
如果你有一段重複出現但會變化的 HTML,可以寫成樣板函式,就像 Python 裡定義函式一樣。例如我們定義以下的樣板函式 render_item
,當傳入 item
後,就會顯示品項標題與敘述:
1 | {% macro render_item(item) %} |
然後在其他樣板中使用:
1 | {% from "macro.html" import render_item %} |
在前面的購物車清單中,我們修改以下寫法:
1 |
|
執行後畫面就會顯示品項與敘述了!

圖 10:樣板函式
這樣不管要渲染幾筆商品資料,都只要呼叫一次 render_item()
,樣板會自動插好 HTML。
模板上下文
當我們使用 render_template()
時,會把某些變數從 Python 傳進樣板中,這些變數就會成為樣板的上下文 (context)。也就是說,樣板可以直接讀取你提供的變數,並根據它們的值來產生動態的 HTML。這個過程類似於 Python 函式呼叫時的參數傳遞,只是這裡是從後端傳給前端的樣板頁面。例如我們可以傳入一個字典物件:
1 |
|
這裡我們建立了一個名為 user
的字典物件,裡面包含兩個欄位:name
和 age
,並將這個 user
傳入樣板中。然後在 profile.html
中這樣寫:
1 | <h1>{{ user.name }}</h1> |
這裡 {{ user.name }}
就代表從 Python 傳進來的 user["name"]
,而 user.age
是存取字典的 age
欄位。

圖 11:模板上下文
注意:Jinja2 支援字典用 變數.屬性
的方式存取,當然也可以寫成 變數["name"]
,但大多數情況下會偏好前者,語意上更像物件。