news 2026/11/4 8:13:09

Flask + SQLite3 手敲Demo:从跑通到避坑的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flask + SQLite3 手敲Demo:从跑通到避坑的完整指南

简介:这份资源是一套面向Python Web开发初学者的Flask入门实践Demo,围绕一个简易博文系统展开,涵盖用户登录登出、博文发布与展示等核心功能,适合刚接触Web框架、希望从零跑通完整项目流程的开发者练手。压缩包共8个文件,约5KB,以html模板、py主程序、sql建表脚本、db数据库文件及css样式为主,另含pyc编译文件,体量轻巧却覆盖了路由视图、Jinja2模板渲染、SQLite3数据存储与session身份验证等关键环节,便于对照理解各模块如何协作。目前已有3088人学习下载,说明其作为入门范例具有一定参考价值。读者可通过这份实例观察Flask应用的最小目录组织方式,理清表单提交、数据库表结构设计与页面渲染之间的衔接关系,并借助错误处理与日志相关思路排查运行问题,为后续独立搭建Web项目打下基础。

1. 从一份“试验楼”Demo说起:Flask + SQLite3 能跑通什么

很多人学 Flask 卡在一个尴尬位置:官方教程能看懂,但真让自己从零搭一个带数据库、带增删改查、带页面跳转的小系统,就不知道从哪下手。这份“试验楼小 Demo”正好填这个缝——它不是脚手架生成器吐出来的空壳,而是一个人一行行敲出来的可运行实例,技术栈就是 Python + Flask + sqlite3,没有 ORM 中间层,没有前端框架,页面用 Jinja2 模板渲染,数据落在本地.db文件里。对刚接触 Python web 开发的人来说,这种“裸写”反而比上来就上 SQLAlchemy 更容易看清请求是怎么进、SQL 是怎么出、模板是怎么渲染的。它适合三类人:想搞懂 Flask 最小闭环的初学者、需要快速验证某个想法的从业者、以及想拿一份干净底稿改造成课程设计或内部工具的人。下面我按“先跑起来、再拆结构、最后填坑”的顺序,把这份 Demo 的用法和边界讲透。

2. 把 Demo 跑起来:环境、依赖与首次启动

2.1 先确认 Python 版本和目录结构

拿到源码包第一件事不是急着pip install,而是先看目录。这类手敲 Demo 通常长这样:入口是app.py或run.py,模板放在templates/,静态资源在static/,数据库文件可能是data.db也可能在首次运行时自动建。先执行python --version,Flask 2.x 要求 Python 3.7 以上,Flask 3.x 建议 3.8 以上;如果你机器上是 3.6,很多新语法会直接报错,别浪费时间排查代码。

# 查看 Python 版本,确认 >= 3.8 python --version # 进入项目目录后,先看结构再动手 ls -la # 典型输出里应能看到 app.py、templates/、static/、requirements.txt

目录里如果有requirements.txt,说明作者留了依赖清单,这是最省事的路径;如果没有,就按 Flask + 标准库 sqlite3 来装,sqlite3 是 Python 自带的,不需要额外安装。这里有个细节:不要用系统全局环境直接装,建个虚拟环境,否则后面版本冲突会很难受。

2.2 虚拟环境与依赖安装

# 创建并激活虚拟环境(Windows 用 venv\Scripts\activate) python -m venv venv source venv/bin/activate # 有 requirements.txt 就按清单装 pip install -r requirements.txt # 没有清单时,最小依赖就一个 Flask pip install flask

装完用pip list确认 Flask 版本。这里常见的翻车点是:作者在 Flask 2.x 下写的代码,你装了 3.x,某些写法(比如@app.before_first_request)已经被移除,启动就报AttributeError。遇到这种情况,优先按报错信息降级 Flask 版本,而不是去改业务代码,因为 Demo 的逻辑往往依赖特定版本的钩子行为。

2.3 首次启动与数据库初始化

# 启动开发服务器,debug 模式方便看报错 flask --app app run --debug # 或者直接 python app.py(取决于入口写法)

启动后浏览器访问http://127.0.0.1:5000。如果页面能出来但一点查询就报“no such table”,说明数据库没初始化。手敲 Demo 一般有两种处理方式:一种是在app.py里用with app.app_context(): db.create_all()之类逻辑自动建表;另一种是单独一个init_db.py脚本。先翻代码找CREATE TABLE关键字,找到后执行对应脚本:

# init_db.py 常见写法:建表 + 插入几条种子数据 import sqlite3 conn = sqlite3.connect('data.db') cur = conn.cursor() cur.execute(''' CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') cur.execute("INSERT INTO items (title, content) VALUES (?, ?)", ("第一条测试数据", "用来验证列表页能否正常渲染")) conn.commit() conn.close() print("数据库初始化完成")

这段脚本的关键点有三个:IF NOT EXISTS保证重复执行不报错;?占位符是参数化查询的写法,后面讲注入时会再提;CURRENT_TIMESTAMP让时间字段自动填充,省得业务代码里手动拼时间字符串。执行python init_db.py后刷新页面,列表能出数据,说明最小闭环通了。

3. 拆开看结构:路由、模板与 sqlite3 的三层配合

3.1 路由层:一个请求从 URL 到函数的映射

Flask 的核心就一句话:URL 规则绑定到视图函数。这份 Demo 里通常能看到列表页、新增页、编辑页、删除动作四类路由。理解它们的关键不是背语法,而是看清methods参数和返回值的关系——GET 返回渲染后的模板,POST 处理表单后重定向。

from flask import Flask, render_template, request, redirect, url_for import sqlite3 app = Flask(__name__) def get_db(): # 每次请求新建连接,简单直接;生产环境应换成连接池或 g conn = sqlite3.connect('data.db') conn.row_factory = sqlite3.Row # 让查询结果支持按列名取值 return conn @app.route('/') def index(): conn = get_db() rows = conn.execute('SELECT * FROM items ORDER BY id DESC').fetchall() conn.close() return render_template('index.html', items=rows) @app.route('/add', methods=['GET', 'POST']) def add(): if request.method == 'POST': title = request.form['title'] content = request.form.get('content', '') conn = get_db() conn.execute('INSERT INTO items (title, content) VALUES (?, ?)', (title, content)) conn.commit() conn.close() return redirect(url_for('index')) # POST 后重定向,避免刷新重复提交 return render_template('add.html')

row_factory = sqlite3.Row这行值得单独说:不设它,查询结果只能按索引row[0]取值,模板里写起来很别扭;设了之后可以row['title'],可读性直接上一个台阶。redirect(url_for('index'))是 POST 处理的标准收尾,少了它,用户刷新页面会触发重复插入,这是新手最常踩的坑之一。

3.2 模板层:Jinja2 的变量、循环与继承

模板不是 HTML 加几个花括号那么简单,它决定了数据怎么呈现、页面怎么复用。这份 Demo 一般会用base.html做骨架,子模板通过{% extends %}继承,导航和页脚只写一次。

<!-- templates/base.html:公共骨架 --> <!DOCTYPE html> <html> <head><title>{% block title %}试验楼{% endblock %}</title></head> <body> <nav><a href="{{ url_for('index') }}">首页</a> | <a href="{{ url_for('add') }}">新增</a></nav> <main>{% block content %}{% endblock %}</main> </body> </html> <!-- templates/index.html:列表页 --> {% extends "base.html" %} {% block content %} <table> <tr><th>ID</th><th>标题</th><th>时间</th><th>操作</th></tr> {% for item in items %} <tr> <td>{{ item['id'] }}</td> <td>{{ item['title'] }}</td> <td>{{ item['created_at'] }}</td> <td><a href="{{ url_for('edit', item_id=item['id']) }}">编辑</a></td> </tr> {% else %} <tr><td colspan="4">暂无数据</td></tr> {% endfor %} </table> {% endblock %}

{% for %}配{% else %}是 Jinja2 的一个实用特性:循环体一次都没执行时走 else 分支,正好用来做空状态提示,比在视图里判断len(items) == 0再传标志位干净。url_for传参时用函数名加参数名,改路由规则时模板不用跟着改,这是它比硬编码 URL 强的地方。

3.3 数据层:sqlite3 直连的取舍

为什么这份 Demo 不用 ORM?因为 sqlite3 是标准库,零依赖,单文件存储,拷走就能跑,特别适合教学和原型。代价是 SQL 要自己写、事务要自己管、迁移要自己想办法。对初学者来说,这个代价换来的是对“一条 SQL 到底怎么执行”的直观认知,值得。

维度sqlite3 直连SQLAlchemy ORM
依赖标准库,零安装需额外安装
学习成本会 SQL 即可需理解模型映射
迁移手写脚本配合迁移工具
适用场景教学、原型、单机小工具中大型项目、多表关联

选型上没有绝对对错,关键看阶段。这份 Demo 的定位就是“看清底层”,所以直连是合理选择;等你把增删改查跑顺了,再换 ORM 迁移成本也不高。

4. 避坑与排查:五个真实会卡住你的问题

4.1 现象:刷新页面重复插入数据 → 原因:POST 后直接返回模板 → 解决:重定向

表单提交后如果视图直接return render_template(...),浏览器地址栏还停在 POST 请求上,用户一刷新就会再提交一次。正确做法是处理完写操作后redirect到 GET 页面,这就是所谓的 PRG 模式(Post-Redirect-Get)。检查你的新增和编辑视图,凡是methods含 POST 的,成功分支都应该是重定向。

4.2 现象:模板里中文乱码 → 原因:响应头编码或文件编码不一致 → 解决:统一 UTF-8

Flask 默认按 UTF-8 输出,但如果你用某些编辑器把模板存成了 GBK,渲染出来就是乱码。排查顺序:先确认.html文件本身是 UTF-8 无 BOM;再确认app.py顶部没有奇怪的编码声明;最后看浏览器开发者工具里响应头的Content-Type是否带charset=utf-8。三处统一,乱码基本消失。

4.3 现象:查询报“database is locked” → 原因:连接没关或并发写冲突 → 解决:确保 close 并缩短事务

sqlite3 是文件级锁,写操作会锁库。如果某个视图开了连接却忘了conn.close(),或者在一个长事务里做了耗时操作,其他请求就会撞上锁。血泪经验是:每个请求内用完即关,写操作commit后立刻close,不要在视图里跨请求持有连接。并发量稍大时,这个限制会很明显,这也是 sqlite3 不适合高并发写入的根本原因。

4.4 现象:sqlite3.OperationalError: no such table→ 原因:工作目录不对或没初始化 → 解决:用绝对路径定位数据库

sqlite3.connect('data.db')是相对当前工作目录的。你在项目根目录启动没问题,换个目录启动就会新建一个空库,自然没有表。稳妥做法是用os.path拼出基于__file__的绝对路径:

import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DB_PATH = os.path.join(BASE_DIR, 'data.db') conn = sqlite3.connect(DB_PATH)

这样无论从哪个目录启动,连的都是同一个库文件。

4.5 现象:表单字段拿不到值报 KeyError → 原因:字段名拼错或没传 → 解决:用 get 兜底并核对 name 属性

request.form['title']在字段缺失时直接抛 KeyError,页面就是 500。两个检查点:HTML 里<input name="title">的 name 和代码里取的键是否完全一致(大小写、下划线都算);非必填字段用request.form.get('content', '')给默认值。养成这个习惯,能省掉大量低级报错。

5. 从 Demo 到能用:参数化查询、分页与部署前检查

5.1 把 SQL 注入堵死在参数化上

手敲 Demo 最容易留下的隐患是字符串拼接 SQL。看到"SELECT * FROM items WHERE id = " + str(id)这种写法,必须改成占位符。sqlite3 的?占位符会把参数当数据而非 SQL 片段处理,这是最省事也最有效的防线。

# 错误示范:拼接字符串,id 可控时直接注入 # cur.execute("SELECT * FROM items WHERE id = " + item_id) # 正确写法:参数化,类型和内容都由驱动处理 cur.execute("SELECT * FROM items WHERE id = ?", (item_id,)) row = cur.fetchone()

注意占位符传参必须是元组,单参数也要写成(item_id,),少了逗号会被当成普通括号,报参数数量不匹配。这个细节我见过太多人栽。

5.2 给列表页加分页,别一次全查

数据一多,SELECT *全量渲染会越来越慢。sqlite3 支持LIMIT和OFFSET,加两个查询参数就能做最简分页:

@app.route('/list') def list_items(): page = request.args.get('page', 1, type=int) # 默认第 1 页 per_page = 10 offset = (page - 1) * per_page conn = get_db() rows = conn.execute( 'SELECT * FROM items ORDER BY id DESC LIMIT ? OFFSET ?', (per_page, offset) ).fetchall() total = conn.execute('SELECT COUNT(*) FROM items').fetchone()[0] conn.close() return render_template('list.html', items=rows, page=page, total_pages=(total + per_page - 1) // per_page)

type=int让 Flask 自动把查询串转成整数,非法值回落到默认 1,省去手动校验。total_pages用向上取整算,模板里据此渲染上一页/下一页链接。分页参数别写死在代码里,抽成常量或配置,后面调每页条数时不用满文件找。

5.3 上线前必须走一遍的检查清单

开发服务器flask run只适合本地,别直接对外。部署前至少确认:debug=False,否则报错页会暴露源码和变量;数据库文件路径用绝对路径且做好备份;静态文件交给前置服务处理;表单加基本校验(非空、长度上限)。这份 Demo 的价值在于帮你把闭环跑通、把坑踩明白,真要上生产,连接管理、并发写入、备份策略都得重新设计。我自己的习惯是:每次改完路由或 SQL,先把增删改查四条路径手动走一遍,再去看日志里有没有 500,这个笨办法救过我很多次。希望这份拆解能帮你少走几步弯路,把 Demo 真正变成自己能改、能扩的底稿。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 8:12:14

深入理解内存屏障与多线程并发编程实践

1. 内存屏障的本质与硬件基础 现代CPU为了提升执行效率&#xff0c;普遍采用乱序执行&#xff08;Out-of-Order Execution&#xff09;技术。当我在实际开发中第一次观察到指令执行顺序与代码顺序不一致时&#xff0c;才真正理解为什么需要内存屏障。这种乱序在单线程环境下完全…

作者头像 李华
网站建设 2026/11/4 8:11:26

智能交通组态平台:低代码技术如何优化城市交通管理

1. 项目概述&#xff1a;当组态技术遇上智能交通在十字路口的红绿灯控制系统背后&#xff0c;在高速公路的电子情报板里&#xff0c;在城市交通指挥中心的大屏上&#xff0c;Ricon组态系统正在724小时不间断地处理着海量设备数据。作为工业自动化领域的"老将"&#x…

作者头像 李华
网站建设 2026/11/4 8:11:26

云GPU平台强化学习环境配置避坑指南

1. 云GPU平台RL训练环境配置全景指南 作为一名在强化学习领域摸爬滚打多年的从业者&#xff0c;我深知环境配置这个看似简单的步骤&#xff0c;往往成为新手复现论文、跑通demo的第一道拦路虎。特别是在云GPU平台上&#xff0c;由于远程环境的特殊性和权限限制&#xff0c;很多…

作者头像 李华
网站建设 2026/11/4 8:10:07

DeepSeek Harness Desktop源码解析:共享UI与Runtime解耦架构

1. 追一个问题&#xff1a;为什么 Harness 需要同时支持 Web 和桌面拿 DeepSeek Harness Desktop 这套源码来说&#xff0c;最吸引我的不是某个模型推理写得有多妙&#xff0c;而是它前端那层“既能在浏览器里跑、又能被本地桌面壳子加载”的设计。你可以直接跑一个纯 Web 版本…

作者头像 李华
网站建设 2026/11/4 8:09:11

PostgreSQL架构解析与性能调优实战

1. PostgreSQL架构全景解读作为从业15年的数据库工程师&#xff0c;我见过太多团队因为对PostgreSQL架构理解不透彻而踩坑。今天我们就来彻底拆解这个经典开源数据库的架构设计&#xff0c;从存储引擎到查询优化器&#xff0c;每个组件都是如何协同工作的。不同于教科书式的架构…

作者头像 李华
网站建设 2026/11/4 8:07:12

EMG信号降噪:ECG干扰处理与算法实现

1. EMG信号降噪的核心挑战与解决思路 肌电信号&#xff08;EMG&#xff09;是研究肌肉活动和神经控制的重要生物电信号&#xff0c;但在实际采集过程中&#xff0c;心脏电信号&#xff08;ECG&#xff09;的干扰就像不速之客&#xff0c;严重影响信号质量。特别是在低强度肌肉收…

作者头像 李华