用純 Python 構建 Web 應用
本教程將帶你從零開始,構建一個交互式的待辦事項清單。
fasthtml
的核心哲學是“回歸初心,大道至簡”。在當今復雜的前后端分離技術棧中 ,它提供了一條返璞歸真的路徑,旨在讓你能用純粹的 Python 構建從簡單到復雜的全棧 Web 應用,徹底告別“前端/后端分離”帶來的割裂感。
第一步:環境準備與項目初始化
首先,我們需要安裝 fasthtml
。它基于 Starlette 和 Uvicorn,性能卓越。
打開你的終端(CMD
、PowerShell
或 Terminal
)并運行以下命令:
pip install python-fasthtml
安裝完成后,創建一個名為 todo_app.py
的文件,準備編寫代碼。
第二步:Python代碼
# -*- coding: utf-8 -*-# ==============================================================================
# 1. 導入與初始化 (Imports & Initialization)
# ==============================================================================
# 從 fasthtml.common 導入所有核心組件。
# fasthtml 巧妙地借助 fastcore.xml 庫,將所有標準HTML標簽(如Div, P, Form)都變成了Python中的類。
# 這讓你可以在純 Python 代碼中直接構建 HTML 結構,無需使用模板語言。
from fasthtml.common import *# AttrDict 允許我們用 `todo.task` 這樣的屬性訪問方式替代 `todo['task']`,讓代碼更優雅。
from fastcore.utils import *# `fast_app()` 會創建應用實例和路由裝飾器。
# app 是一個基于 Starlette 的高性能 ASGI 應用實例。
# rt 是路由對象,用于將 URL 路徑(如'/')與處理函數關聯起來。
app, rt = fast_app()# ==============================================================================
# 2. 數據存儲 (Data Store)
# ==============================================================================
# 使用帶唯一ID的列表來存儲數據。
# 原始代碼使用 list.index(),當任務重名時會導致錯誤。唯一ID是更健壯的實踐。
todos = []
next_id = 0# ==============================================================================
# 3. 組件化渲染 (Componentized Rendering)
# ==============================================================================
# 將UI元素封裝成函數是 fasthtml 的核心優勢之一,這極大地促進了代碼的復用和組件化。
# 這個函數就是一個可復用的 “ToDo Item” 組件。
def render_todo(todo: AttrDict):"""根據傳入的 todo 對象,生成對應的 HTML 列表項 <li>。"""return Li(Div(# --- HTMX 驅動的動態交互 ---# HTMX 是 fasthtml 實現動態交互的秘密武器。# 它讓任何HTML元素都能向服務器發請求,并用返回的HTML片段更新頁面局部,無需刷新。Input(type="checkbox",checked=todo.done,# hx-post: 點擊時,向指定URL發送POST請求。hx_post=f"/toggle/{todo.id}",# hx-target: 指定服務器返回的HTML應該更新哪個元素(使用CSS選擇器)。# 'closest li' 表示“找到最近的父級<li>元素”。hx_target="closest li",# hx-swap: 指定如何更新目標元素。# 'outerHTML' 表示用返回的內容完整替換整個目標元素(包括<li>標簽自身)。hx_swap="outerHTML",cls="mr-2"),Span(todo.task, cls=("line-through text-gray-500" if todo.done else "")),Button("?",# hx-delete: 點擊時發送 DELETE 請求。hx_delete=f"/delete/{todo.id}",hx_target="closest li",# hx-swap='delete': 一個特殊值,它會直接將目標元素從頁面移除。hx_swap="delete",cls="text-red-500 ml-auto"),cls="flex items-center py-2 border-b"))# ==============================================================================
# 4. 頁面路由 (Page Routes)
# ==============================================================================
@rt('/')
def get():"""定義根路由('/')的處理器,返回構成整個頁面的Python對象。"""return Html(Head(Title('FastHTML ToDo App'),# 引入 Tailwind CSS。fasthtml 可以輕松集成任何CSS框架,如 DaisyUI。Script(src="https://cdn.tailwindcss.com"),# 引入 HTMX 庫本身。這是實現所有 "hx-*" 魔法的基礎。Script(src="https://unpkg.com/htmx.org@1.9.12"),),Body(Div(H1("我的 FastHTML 待辦事項", cls="text-2xl font-bold mb-4 text-center"),Form(Input(type="text", name="task", placeholder="輸入新任務...", required=True, cls="border p-2 mr-2 flex-grow"),Button("添加任務", type="submit", cls="bg-blue-500 text-white p-2 rounded hover:bg-blue-600"),# 提交表單時,向 /add URL 發送 POST 請求。hx_post="/add",# 返回的HTML片段將被插入到 id="todo-list" 的元素中。hx_target="#todo-list",# 'beforeend' 表示在目標元素的末尾追加內容。hx_swap="beforeend",# 請求成功后清空輸入框,提升用戶體驗。hx_on="htmx:afterRequest: this.reset()",cls="flex mb-8"),# 初始加載時,渲染所有已存在的待辦事項。# 這里的列表推導式完美體現了邏輯與視圖的統一。Ul([render_todo(t) for t in todos], id="todo-list"),cls="container mx-auto p-8 max-w-lg bg-white rounded-lg shadow-lg mt-10"),cls="bg-gray-100"))# ==============================================================================
# 5. API 路由 (API Routes for HTMX)
# 這些路由只返回 HTML 片段,供 HTMX 進行局部更新。
# ==============================================================================
@rt('/add')
def post(task: str):"""處理添加任務的請求。FastHTML 會自動將表單字段'task'映射到函數參數。"""global next_idnew_todo = AttrDict(id=next_id, task=task, done=False)todos.append(new_todo)next_id += 1# 僅返回新任務的HTML片段,HTMX會將其追加到列表中。return render_todo(new_todo)@rt('/toggle/{id:int}')
def post(id: int):"""根據ID切換任務的完成狀態。"""todo = next((t for t in todos if t.id == id), None)if todo:todo.done = not todo.done# 返回更新后的任務HTML,HTMX會用它替換掉舊的列表項。return render_todo(todo)return ""@rt('/delete/{id:int}')
def delete(id: int):"""根據ID刪除任務。"""todo_to_delete = next((t for t in todos if t.id == id), None)if todo_to_delete:todos.remove(todo_to_delete)# 對于 hx-swap="delete",只需返回一個HTTP 200 OK的空響應即可。return ""# ==============================================================================
# 6. 啟動服務 (Run the Server)
# ==============================================================================
if __name__ == "__main__":# 啟動 Uvicorn 服務器來運行我們的 ASGI 應用。serve()
第三步:運行你的應用
- 保存好
todo_app.py
文件。 - 在終端中,確保你位于該文件所在的目錄。
- 執行命令:
python todo_app.py
- 你會看到服務器啟動的提示。
- 在瀏覽器中打開 http://localhost:5001 (或其他端口),你將看到你的應用界面。
第四步:為什么選擇 fasthtml
fasthtml
的優勢非常明確:
- 純粹的 Python:為 Python 開發者,尤其是后端和數據科學家,提供了一條無需深入學習前端框架(如 React, Vue)就能構建全功能 Web 應用的捷徑。
- 簡化但強大:它將 htmx 的強大功能無縫集成到 Python 中,通過簡單的函數調用實現復雜的動態交互,極大地降低了全棧開發的門檻。
- 性能優異:基于 Starlette 和 Uvicorn,具備處理生產環境需求的異步和高性能特性。
- 快速原型驗證:對于需要快速將 AI 模型等想法轉化為可交互原型的場景,
fasthtml
是比 Streamlit 或 Gradio 更靈活、更接近生產形態的選擇。
第五步:下一步探索與部署上線
ToDo 應用現在已經在本地運行了。接下來,你可以嘗試:
- 功能擴展:添加“編輯”功能,這會涉及到
hx-get
(獲取編輯表單)和hx-put
(提交更新)。 - 異步任務:嘗試集成一個外部 API。或者像 AI 圖片生成示例一樣 ,處理耗時任務,并使用
hx-trigger='every 1s'
進行輪詢。 - 實時通信:對于需要多人協作的應用,可以研究
fasthtml
對 WebSocket 的一流支持。
部署你的應用
fasthtml
應用是標準的 ASGI 應用,可以部署到任何支持 Python 的平臺
- Railway:通過 Git 自動化部署,非常適合小型項目。
- Replit:一個在線 IDE,可以在瀏覽器中一鍵開發和部署,適合學習和實驗。
- HuggingFace Spaces:如果你在構建 AI 應用或模型 Demo,這是理想選擇,它提供了優化的環境和免費計算資源。
- 其他:你也可以使用 Vercel、PythonAnywhere 或任何云服務器(VPS)進行傳統部署。