首页 / 资讯中心 / 文章详情

Flask基础学习指南:路由、模板、数据库与蓝图实战

Flask基础学习指南:路由、模板、数据库与蓝图实战 ★ FEATURED ARTICLE
我刚入行那会儿也跟很多人一样在 Django 和 Flask 之间来回纠结。后来发现能让你把脉络理清的框架才是入门首选。Flask 就是这样的框架。这篇 Flask 基础学习指南不打算讲花哨的扩展只聚焦路由、模板、请求响应、数据库、蓝图这五根主线边讲原理边给可复制的代码。它能做什么快速搭接口、写管理后台、做个人博客、做产品原型甚至接一个外部系统回调都行。最重要的是它把 HTTP 请求如何进入 Python 代码、如何被路由函数接住、如何把响应返回给浏览器暴露得非常直观。适合谁适合刚学完 Python 基础、想接触 Web 开发的初学者也适合写过一两个 Flask 脚本但没系统梳理过路由和模板继承的人。如果你是第一次接触 Web 框架这个指南就是一张手把手的清单。1. 先把 Flask 是什么讲明白从 WSGI 到你的第一个 Hello World1.1 为什么是 Flask 而不是 Django / FastAPI每次有人问“新手学哪个 Python Web 框架”总有人丢出一句话小项目用 Flask大项目用 Django。这句话听着对实际没什么指导意义。问题是你压根不知道自己的项目未来会长多大而且多数人根本等不到“大项目”那一天就已经放弃了。Flask 官方的定位是“micro framework”微框架。微不代表功能弱只代表内核精简。核心就把两件事做好接收 HTTP 请求调用你写的 Python 函数再把返回值作为响应发出去。模板引擎、数据库、表单校验、登录这些官方不做强制集成全交给扩展和你自己选择。Django 是“全家桶”思路内置 Admin、ORM、迁移工具、权限体系路径已经铺好但正因为它铺得太好新手很难理解请求到底是怎么进来的。FastAPI 用类型注解和 Pydantic 自动生成接口文档从理念上很现代但对初学者来说异步模型和数据校验规则会夹杂进来反而把“路由和视图”这个最核心的概念冲淡了。注意我说这些话不是否定 Django 或 FastAPI。只是想表达Flask 更适合作为“第一根拐杖”。等你在 Flask 里亲手实现了登录、模板渲染、数据库读写再去用 Django你会发现很多概念是相通的只是别人帮你做完了。另外Flask 还有一个很现实的优点依赖少。一个全新的虚拟环境加一个 Flask 包5 分钟就能跑起来。这对刚接触部署、还没掌握容器化的新手来说心理门槛和排错成本都更低。1.2 环境准备虚拟环境与 pip 安装网上很多教程上来就让你pip install flask也不管安装到哪里。我刚开始也是这么干的后来包版本一多就乱套。这里强烈建议从第一天就使用虚拟环境。虚拟环境的作用就是给每个项目单独开一个 Python 小屋里面装的第三方库互不打扰。Python 自带的venv模块足够用不需要额外装 virtualenv。命令如下mkdir flask-guide cd flask-guide python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活后你在终端提示符前应该能看到(venv)字样。然后安装 Flaskpip install flask装完后顺手验证一下python -c import flask; print(flask.__version__)如果命令能输出版本号说明环境正常。如果报 ModuleNotFoundError多半是当前终端没在虚拟环境里或者你用了pip3装到了系统 Python。这两个问题最好一次性解决重新激活并再安装一遍顺便检查一下 Python 版本。我这里建议直接用 Python 3.8 以上版本老版本虽然也能装上但部分新特性用不了排查问题时反而多一层干扰。1.3 写第一个 Flask 应用并理解启动流程在项目目录里新建一个app.py写入from flask import Flask app Flask(__name__) app.route(/) def index(): return Hello, Flask! if __name__ __main__: app.run(debugTrue)然后运行python app.py浏览器打开http://127.0.0.1:5000就能看到那句 Hello。这段代码信息量很大。Flask(__name__)里的__name__不是随便填的它让 Flask 能找到当前模块所在路径进而定位templates和static目录。app.route(/)是把根路径交给index函数处理。app.run(debugTrue)开启调试模式代码修改后服务会自动重载同时出错时会提供交互式调试页面。注意debugTrue只能在开发环境用。一旦部署到公网必须关掉否则别人可以借助调试器在你服务器上执行任意代码。这个点值得刻在脑子里。如果启动时端口被占用可以用app.run(port5001)换个端口也可以在命令行里设置环境变量指定。这些细节等到第七节一起讲。2. 路由与视图Flask 的 URL 分发机制到底怎么工作2.1 路由规则与动态参数一个 URL 模板搞定同一类页面路由就是 URL 到函数之间的“接线板”。你访问/Flask 找到根路径的视图函数你访问/aboutFlask 找到对应的视图函数。实际项目里URL 不可能全是静态的比如博客文章有/post/1、/post/2……如果为每一篇文章写一个路由那代码会爆炸。Flask 支持在路由里写动态参数app.route(/post/int:post_id) def show_post(post_id): return f这是文章 {post_id}这里的int:post_id表示这一部分必须是整数并把值作为post_id参数传给函数。如果访问/post/abcFlask 会直接返回 404因为转换器匹配失败。常用的 URL 转换器有转换器匹配类型示例string不包含斜杠的字符串usernameint正整数int:user_idfloat浮点数float:pricepath包含斜杠的路径path:file_path因为string是默认转换器所以/username等价于/string:username。path:file_path常用于静态文件、嵌套分类路径比如/docs/path:subpath可以匹配/docs/python/getting-started。2.2 请求方法的组合与视图函数拆分一个 URL 经常会承担多种语义获取用户信息用 GET创建用户用 POST。Flask 默认路由只响应 GET如果想同一路径支持多种方法需要传入methodsapp.route(/users, methods[GET, POST]) def users(): if request.method GET: return 列出用户 elif request.method POST: return 创建用户写多了你会发现把多种命令塞进一个视图函数代码很快就乱了。更清晰的做法是拆成两个路由用不同函数处理app.route(/users, methods[GET]) def list_users(): return 列出用户 app.route(/users, methods[POST]) def create_user(): return 创建用户Flask 2.0 之后也提供了app.get和app.post这样的装饰器底层等价于methods[GET]和methods[POST]写起来更省事。不过基础学习阶段我更建议先用显式的methods把“HTTP 方法”这个概念记牢。要注意的是POST 请求携带的数据不会出现在 URL 里而是放在请求体。对应在 Flask 里用request.form或request.json拿。这部分在第四小节展开。2.3 路由匹配顺序为什么静态路径最好放在动态路由前面Flask 内部使用 Werkzeug 的路由表来做 URL 匹配不是简单地按“谁先定义谁匹配”。它有一套转换器优先级排序但新手不一定完全记得住。为了少出问题我的习惯是所有静态路径放在动态路径前面。举个例子如果你有两个规则我一般会把静态的、完全独立的路径放在前面动态的放后面。比如/post/draft这种明确路径就放在/post/int:post_id前面。这样写自然没问题。但如果把动态规则放前面同时把转换器改成string:post_id访问/post/draft时draft就会作为post_id传给show_post逻辑显然错了。基础阶段别太信赖隐式优先级直接按静态优先、动态靠后写能避开一整个类别的坑。3. 模板与静态文件Jinja2 引擎的实战用法3.1 模板语法与自动转义先看一眼这些关键符号视图函数里直接返回字符串适合做接口。但页面结构一复杂字符串拼接会让代码变成灾难。Flask 默认使用 Jinja2 模板引擎在templates目录下放 HTML 文件视图函数用render_template去渲染。from flask import render_template app.route(/hello/name) def hello(name): return render_template(hello.html, namename)hello.html长这样!doctype html titleHello/title h1Hello {{ name }}!/h1 {% if name|length 5 %} p名字有点长/p {% else %} p名字真短/p {% endif %}{{ name }}是表达式输出{% %}是控制结构。Jinja2 还支持过滤器比如{{ name|upper }}转大写{% for %}循环列表。这里最容易被忽略的是自动转义。Jinja2 默认把 HTML 特殊字符转成实体比如变成lt;。如果你在模板里渲染的是用户提交的内容这个机制能直接挡掉 XSS 攻击。但如果你真的想输出一段 HTML要写{{ content|safe }}。一旦用了|safe内容就完全由你信任度决定。注意想要把 HTML 字符串原样渲染优先考虑用 Markdown 转换或白名单过滤别随意加|safe。我在自己项目里见过不少程序员因为图省事用了|safe之后被渲染脚本轰炸。3.2 模板继承一次改动全站生效很多页面都有公共头尾导航。初学者最容易做成每个页面复制一遍 HTML看一次可以页面多起来就是噩梦。Jinja2 的模板继承能解决这个问题。新建base.html!doctype html html head {% block head %} link relstylesheet href{{ url_for(static, filenamestyle.css) }} {% endblock %} title{% block title %}默认标题{% endblock %}/title /head body nav公共导航/nav {% block content %}{% endblock %} /body /html子页面这样写{% extends base.html %} {% block title %}文章列表{% endblock %} {% block content %} p这里是正文/p {% endblock %}子模板通过extends继承母版再用block覆盖对应区域。没覆盖的 block 会沿用母版内容。这套机制最大的好处是想改全站导航只要改base.html一处所有子页面立刻生效。我在给某个内部管理系统做前端壳时就是靠这个方式把几十个页面的公共部分全部收拢了。3.3 url_for 与静态文件别在模板里硬编码路径模板里一旦写死/static/css/style.css后面要调整 URL 前缀或者套 CDN 就会很痛苦。Flask 提供了url_for函数根据端点和参数反向生成 URL。在模板里引用静态资源link relstylesheet href{{ url_for(static, filenamecss/style.css) }}在 Python 代码里也可以反向构造路由 URLfrom flask import url_for # 假设有视图函数 hello(name) url_for(hello, namealice)结果就是/hello/alice。这样做的好处是你只要改路由规则所有引用它的地方会自动更新。静态文件默认放在项目的static目录。Flask 会自动为它生成/static/path:filename路由。有一个常见坑是有人把自己的静态资源根路径写进app.route(/static/path:filename)去覆盖默认行为结果项目升级后行为变得奇怪建议大家不要这么干直接用 Flask 默认前缀。4. 请求、响应与表单处理理解页面背后的数据流转4.1 request 对象GET、POST、JSON 数据都别搞错Flask 在每个视图函数里无需自己解析 HTTP 报文直接用全局代理对象request就能拿到客户端数据。常见数据来源request.args: URL 查询参数对应 GET 请求的?keywordpythonrequest.form: 表单字段常见于application/x-www-form-urlencodedrequest.json: JSON 请求体需要请求头Content-Type: application/jsonrequest.files: 上传文件request.headers: 请求头一个常见错误是把 GET 参数用request.form去拿或者反过来。虽然报错不明显但数据始终是空。判断请求方法就靠request.method。from flask import request app.route(/search) def search(): keyword request.args.get(keyword, ) return f你搜索了{keyword}对于必填参数如果你写request.form[username]而客户端没传这个字段Flask 会返回 400 Bad Request。这时你其实可以自己捕获并给出更友好的提示username request.form.get(username, ).strip() if not username: return 用户名不能为空, 4004.2 返回 JSON、重定向与自定义响应写前后端分离接口时最常用的是 JSON。Flask 提供jsonifyfrom flask import jsonify app.route(/api/ping) def ping(): return jsonify({status: ok, code: 0})Flask 2.2 之后直接返回字典也会自动 JSON 序列化不过我还是喜欢显式写jsonify意图更清楚。如果要在接口里做页面跳转用redirect配合url_forfrom flask import redirect, url_for app.route(/old) def old(): return redirect(url_for(new_page))默认返回 302 临时重定向。如果做 SEO 或资源迁移需要永久 301可以传 codereturn redirect(url_for(new_page), code301)。Flask 的响应对象也支持三元组写法return body, status, headers。比如return 资源不存在, 404, {X-Custom-Header: value}这样写的好处是不用显式构造 Response 对象代码更紧凑。但要是响应头很多或逻辑复杂就老老实实导入Response去构造。4.3 表单提交与闪现消息给用户一个明确的反馈表单校验失败后最好把错误原因提示给用户。Flask 的flash组件是用来实现这个目的的它把消息存在 Session 里下一次请求还能读取读取一次后自动清除。登录示例from flask import render_template, request, flash, redirect, url_for app.route(/login, methods[GET, POST]) def login(): if request.method POST: name request.form.get(name, ) if name admin: flash(登录成功) return redirect(url_for(index)) else: flash(用户名错误) return render_template(login.html)模板里这样取消息{% with messages get_flashed_messages() %} {% if messages %} ul {% for message in messages %} li{{ message }}/li {% endfor %} /ul {% endif %} {% endwith %}注意flash依赖 Session所以用之前需要设置app.secret_key否则会出现运行时报错。学习阶段设置一个随机字符串就行app.config[SECRET_KEY] dev-secret-key5. 数据库与模型用 Flask-SQLAlchemy 把数据持久化5.1 初始化扩展与定义模型先跑通 SQLite 再说没有数据库Flask 应用和“计算器”没什么区别每次重启数据全没。Flask 本身不带 ORM但生态里的 Flask-SQLAlchemy 是事实标准。它把 SQLAlchemy 的初始化、会话管理都封装好了新手也能快速上手。安装pip install flask-sqlalchemy基础代码from flask import Flask from flask_sqlalchemy import SQLAlchemy app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///app.db app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db SQLAlchemy(app) class User(db.Model): __tablename__ users id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(80), nullableFalse, uniqueTrue) email db.Column(db.String(200), nullableFalse)SQLALCHEMY_DATABASE_URI是数据库连接地址。这里先选最轻量的 SQLite一个文件搞定不需要额外服务。想切到 MySQL 或 PostgreSQL 时只要改这个 URI 和安装对应驱动模型代码基本不用动。建表和删表with app.app_context(): db.create_all()注意这段代码必须放在with app.app_context():里执行。为什么要这样Flask-SQLAlchemy 在 3.0 之后要求模型元数据绑定到应用上下文脱离视图函数执行数据库操作时时不时会遇到RuntimeError: Working outside of application context。我在命令行里排查数据库问题时踩过好几次这个坑。很多人直接写在模块末尾但没有 app_context会报错。解决办法就是养成习惯在所有命令行脚本或交互式操作外面包一层with app.app_context():视图函数里因为请求本身处于应用上下文里反而不需要额外包裹。5.2 增删改查与事务一张表的基本操作插入数据user User(usernamealice, emailaliceexample.com) db.session.add(user) db.session.commit()查询# 查询全部 users User.query.all() # 按条件过滤取第一个 user User.query.filter_by(usernamealice).first() # 按主键取 user db.session.get(User, 1)修改和删除user.email newexample.com db.session.commit() db.session.delete(user) db.session.commit()这里面最容易忘的是db.session.commit()。很多初学者写完add后以为数据已经存了重启应用再看傻眼。因为 SQLAlchemy 的事务默认在你 commit 时才真正提交。如果忘记 commit数据只在内存里过一下会话就没了。注意开发调试时如果不想手动 commit也可以用db.session.flush()先把 SQL 发出去但不提交事务。更常见的做法是写一个全局after_request钩子自动 commit但那属于进阶技巧甚至有风险基础阶段建议老老实实手动 commit。5.3 关系模型与懒加载一对一还是一对多现实中的数据往往不是一张表孤立的。用户和文章就是典型的一对多关系。定义class Post(db.Model): __tablename__ posts id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(100), nullableFalse) user_id db.Column(db.Integer, db.ForeignKey(users.id)) author db.relationship(User, backrefposts)外键user_id指向users.id。通过关系字段author就能在文章对象上直接拿到作者post Post.query.first() print(post.author.username)反向访问也很直接user User.query.first() for post in user.posts: print(post.title)SQLAlchemy 默认使用懒加载访问到关系字段时才执行查询。这个方法在写循环时有个坑循环里不断访问user.posts会引发 N1 查询问题性能会很差。基础阶段先知道这个现象需要用joinedload这类预加载策略解决等真正遇到性能瓶颈再去研究。6. 蓝图与项目组织从单文件走向可维护工程6.1 为什么要把路由拆分到蓝图Flask 的最小可以只有一个app.py但如果全部代码都堆在单文件里路由超过 20 个、模型超过 5 个之后每次改动都要在几百行里找目标函数非常容易出错。Blueprints中文叫蓝图是 Flask 官方提供的模块化工具。它可以把一组相关的路由、模板、静态文件打包到一个 Python 模块里。比如用户认证相关的路由放auth.py博客内容相关的放blog.py。最后应用启动时统一注册。可以把蓝图理解成电器里的独立开关面板每个面板功能清晰最终再接到总路线上。它和 Django 的 app 概念很像但更轻。6.2 蓝图注册与工厂函数项目结构怎么搭推荐一个最小但清晰的结构app/ ├── __init__.py ├── auth.py ├── blog.py ├── templates/ └── static/在auth.py里创建蓝图from flask import Blueprint, render_template auth_bp Blueprint(auth, __name__, url_prefix/auth) auth_bp.route(/login) def login(): return render_template(login.html)Blueprint(auth, __name__)第一个参数是蓝图名它会影响后续url_for(auth.login)的生成。url_prefix/auth表示蓝图里/login最终映射为/auth/login。在app/__init__.py里用工厂函数创建应用并注册蓝图from flask import Flask def create_app(): app Flask(__name__) from .auth import auth_bp from .blog import blog_bp app.register_blueprint(auth_bp) app.register_blueprint(blog_bp) return app运行入口from app import create_app app create_app() if __name__ __main__: app.run(debugTrue)用工厂函数的好处是测试时可以随时创建独立实例生产部署时也可以按环境传入不同配置。这一步会让 Flask 项目从“脚本”变成“工程”。6.3 循环导入的根源和解决方案把代码拆到多个文件后最常见的问题是循环导入。比如在auth.py里写了from app import app然后试图访问app.config而根模块又要导入auth两个模块互相依赖最终抛ImportError。根本原因模块初始化顺序错乱。解决方案就是上面这套结构蓝图模块只负责定义蓝图不直接持有 Flask 实例根模块负责创建实例并注册蓝图。依赖关系从“双向”变成“单向”循环导入问题自然会消失。我在给以前一个内部工具重构时就是被循环导入折腾了两天。后来把所有 Flask 实例的创建都收进create_app问题才彻底解决。7. 常见坑与排错实录Flask 新手最容易踩的六个问题7.1 环境、端口与热重载问题启动就报ModuleNotFoundError: No module named flask九成是环境没激活。终端里输入which python看一下路径是否指向项目下的venv如果不对就重新激活。Windows 下常见问题是老项目用了python而系统装的是py可以统一用py -m venv venv。端口被占用的报错一般是OSError: [Errno 98] Address already in use。排查方式是lsof -i :5000找到占用进程后 kill 掉或者干脆把 Flask 开发服务改到其他端口app.run(port5001)修改代码不热重载先确认你是否用了debugTrue。如果已经用了还是不生效检查 IDE 缓存或浏览器缓存有时候浏览器把旧页面缓住了按 CtrlF5 强刷再看。7.2 路由 405/404 与表单 400 问题访问一个存在但方法不对的路径报 405 Method Not Allowed。原因多半是你 URL 写对了但是 GET 请求打到了一个只允许 POST 的路由或者反过来。解决方式是给路由加上methods[GET, POST]或者把对应函数拆开。访问路径返回 404可能是静态路径和动态路由冲突也可能是url_prefix没写对。建议先把蓝图路由从 URL 前缀里拆出来测试逐步定位。表单数据提交后报 400 Bad Request常见于request.form[xxx]拿一个不存在的字段。用request.form.get(xxx)替代并且先检查页面表单name属性是否和服务端一致。很多拼写错误就是不仔细看导致的。7.3 模板、数据库与部署问题速查表我把新手阶段高频踩到的问题整理成一张速查表症状常见原因快速处理模板不生效总是显示旧内容生产模式、模板缓存开发时开debugTrue需要时设置app.config[TEMPLATES_AUTO_RELOAD]True刷新后数据丢失没调用db.session.commit()在增删改后补上 commit中文 JSON 变成\uXXXXFlask JSON 默认 ASCII 输出返回前设置app.json.ensure_ascii False文件上传后文件名为空表单里enctype没写multipart/form-data检查前端表单使用request.files生产部署后静态文件 404Nginx 没把 /static 指到项目目录配置反向代理或使用 CDN重启后端口被占用开发服务进程没退出用lsof -i :5000查看进程并处理这张表不能覆盖所有问题但足够帮初学者把最打击信心的问题快速抹平。如果你遇到的场景不在表里先别急着百度打开终端看重试日志或 Flask 控制台输出通常错误信息已经把方向指得很清楚了。排查本身就是编程能力的一部分耐心点比复制粘贴答案收获大。调试时可以临时开一下 debug 模式但上线前记得关掉。我最后的建议是学 Flask 别只看教程最好拿一个小需求完整走一遍。比如做一个“个人书签”页面把视图、模板、表单、数据库、蓝图全串起来。我当年就是写完一个内部工具后对这些基础概念才有了质的理解。这篇指南里提到的坑我也基本都踩过一遍希望你看到的时候能少一次熬夜。
阅读完成 · 觉得有帮助?
咨询建站