ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flask入门实践:从虚拟环境到路由调试的完整指南

Flask入门实践:从虚拟环境到路由调试的完整指南 1. 动手前的准备环境搭建与版本选择1.1 虚拟环境为什么不能直接在系统 Python 里装很多第一次接触 Flask 的同学上来就一句pip install flask装完就写代码写到第二周就开始出事。我见过太多人一个电脑上有三四个项目每个项目依赖的 Flask 版本不一样有的还混着 Django、FastAPI最后 pip list 一拉上百个包谁也不敢动一动就崩。这个问题的根源就是全局环境被“污染”了。正确做法是从一开始就创建虚拟环境。虚拟环境的本质就是给项目单独划一块地盘把依赖装进这个隔离目录里不打扰系统 Python也不被其他项目打扰。Python 3.3 以后内置了venv模块不需要额外安装直接用就可以。创建和激活虚拟环境的命令在不同系统上稍有区别# 创建虚拟环境venv 是环境目录名可以随便取 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate激活之后你会看到命令行前面多了一个(venv)前缀这就代表你当前已经处于虚拟环境之中。这时候再执行pip install flask装的就是项目私有的 Flask。以后想换电脑或者交给同事开发只需要把这个项目目录拷过去然后重新创建一个虚拟环境、安装requirements.txt里的依赖就行完全不会跟系统环境纠缠。顺便说一句如果你用 PyCharm新建项目时它会默认帮你创建虚拟环境用 VS Code 的话选完解释器后终端会自动激活虚拟环境。但即便如此我仍然建议你手动走一遍命令行创建环境的流程因为这一步是 Python Web 开发的基本功后面部署上线、写 CI 脚本都要用到。1.2 安装 Flask 与版本选择创建好虚拟环境并激活之后安装非常简单pip install flask如果你想指定版本pip install flask3.0.3这里我想多说两句版本选择的问题。Flask 目前主线的版本是 2.x 和 3.x但这两个大版本之间的差异对新手来说几乎没有感知。3.x 主要是移除了 Python 2 和旧版 Python 3 的支持要求 Python 3.9 以上另外内部结构做了一些调整。如果你用的是 3.1 以上的 Flask基本上就是最新的 3.x 版本了天然适配 Python 3.10 的环境。安装完成后怎么验证不要直接进交互式 Python 写import flask然后听天由命我告诉你一个更直观的方式import flask print(flask.__version__)能打印出版本号就说明一切正常。你还可以顺手看一下 Flask 依赖了哪些基础库。执行pip list会看到Werkzeug和Jinja2这两个是 Flask 的左膀右臂Werkzeug 负责处理 HTTP 协议层面的东西比如请求解析、响应封装、路由匹配Jinja2 是模板引擎负责渲染 HTML 页面。Flask 本身只是把这两块整合起来提供了一个更简洁的编程接口。理解这一点对你后面排查问题会有帮助很多时候你遇到的其实是 Werkzeug 层面的问题。另外国内网络环境下 pip 下载慢可以换用国内镜像源pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple永久配置镜像源的方式是修改 pip.conf 或者命令行执行pip config set global.index-url但初学者其实不需要折腾这个知道有 -i 参数就够了。2. 三行核心代码最小 Flask 应用背后发生了什么2.1 第一个应用的完整代码与逐行拆解不管你看过多少 Flask 教程绕不开的永远是下面这段代码。我先把完整代码贴出来然后逐行拆开讲你别看它简单这里面每一行背后都藏着知识点能把这些搞清楚你后面学 Flask 就轻松一半。from flask import Flask app Flask(__name__) app.route(/) def hello(): return Hello, Flask!第一行是导入 Flask 类这个没问题。真正值得思考的是第二行Flask(__name__)里的__name__是什么为什么要传它简单说__name__是当前模块的名字。如果你直接从命令行运行python app.py那么这个值是__main__如果这个文件被别的地方import那么这个值就是文件名不含 .py 后缀。Flask 需要这个参数来定位项目的根目录进而找到static静态资源文件夹和templates模板文件夹这些默认资源的位置。所以这一步是必须的但写法其实有两种app Flask(__name__) app Flask(my_app)传模块名字符串本质上是一样的效果。官方推荐的写法是__name__因为这样最不容易出问题不管文件叫什么、在哪儿被导入都能正常工作。如果你写死一个字符串“my_app”那么当你的文件被移动到别的目录时Flask 找资源文件就可能找错位置。第三行是装饰器。app.route(/)表示把下面的函数注册到 Flask 的路由表里并且 URL 是/。如果你访问http://127.0.0.1:5000/Flask 就会找到hello这个函数执行它然后把返回值作为 HTTP 响应发送给浏览器。2.2 字符串如何变成 HTTP 响应新手最容易在这里产生一个疑问return Hello, Flask!返回的是一个普通字符串它怎么就变成了浏览器能识别的 HTTP 响应答案是 Flask 在背后帮你做了自动包装。它的内部逻辑是这样的视图函数返回的任何值都会被打包成一个Response对象。字符串会被自动加上 HTTP 状态码 200以及Content-Type: text/html; charsetutf-8这样的响应头。这也是为什么你在浏览器里访问能正常看到“Hello, Flask!”这行字。如果你想去掉默认行为更精细地控制返回内容可以这样写from flask import make_response app.route(/) def hello(): resp make_response(Hello, Flask!, 200) resp.headers[Content-Type] text/plain return resp对于第一个应用来说直接 return 字符串就够了但你要知道这个“简洁”背后是有包装机制的。理解这一点之后你看到别人 returnjsonify(...)返回 JSON 数据就不会觉得是什么魔法了本质上也是生成一个带Content-Type: application/json的 Response 对象。2.3 理解 ifname main在 Flask 项目里你几乎一定会看到这段保护性代码if __name__ __main__: app.run(debugTrue)没接触过的人可能会觉得这句话很玄乎。其实它的作用是只有当这个文件被当作主程序直接运行时才启动开发服务器。如果这个文件是被其他文件 import 进来的那么__name__的值不是__main__这段启动代码就不会执行。为什么要有这个保护因为一个 Web 项目往往会有多个模块后面的代码会分文件组织。如果某一个模块被 import 时它自己带着app.run()就直接执行了那一运行程序就立刻启动一个服务器这会非常混乱。在实际开发中我更建议你把启动方式改成使用flask run命令而不是直接python app.py。使用flask run的时候它通过环境变量FLASK_APP找到你的应用实例export FLASK_APPapp.py flask runWindows 下改成set FLASK_APPapp.py即可。这种方式是 Flask 官方推荐的好处是行为和部署时更接近而且不用在代码里写死app.run()。不过对于第一个人项目直接python app.py也无伤大雅先把跑通这件事搞定再慢慢换姿势。3. 路由系统让你的第一个应用响应不同 URL3.1 路由的本质与设计思路第一个应用写完之后你自然而然地会想如果我在浏览器里访问/about或者/contact能不能也看到内容这时候就需要理解路由系统。路由的本质是“URL 到函数的映射关系”。它解决的核心问题是当用户请求某个 URL 时服务器怎么知道该调用哪段代码来处理。没有框架的话你得自己写一堆if path /about: ...这样的判断。而现在一个装饰器就搞定了app.route(/) def hello(): return Hello, Flask! app.route(/about) def about(): return 这是关于页 app.route(/contact) def contact(): return 这是联系页你可以把路由表理解成一张映射表Flask 在收到请求后去查这张表实际实现是基于路由规则构建的匹配树比查表高效得多找到匹配的 URL就调用对应的函数。函数名叫什么其实无所谓重要的是 route 装饰器里指定的路径以及函数返回的内容。这里我想提醒一个细节是Flask 默认情况下/about和/about/是不等价的。如果你访问/about没问题但访问/about/会 404。如果你想让两者都生效可以在路由里加上strict_slashesFalse参数。但我的建议是如果不是特殊需求保持默认规则就挺好接口设计时统一一种 URL 风格能避免很多麻烦。3.2 动态参数与 URL 转换器静态 URL 能应对的只是少数固定页面真实场景里更多时候需要动态地址。比如一个博客系统你要用同一个函数处理不同的文章 ID/post/1、/post/2、/post/3。不可能为每一篇文章写一个路由吧这时候就需要动态参数。app.route(/post/int:post_id) def show_post(post_id): return f正在查看文章文章编号{post_id}这里的尖括号int:post_id叫动态参数。它分为两部分int是类型转换器post_id是变量名。Flask 内置了多种转换器string默认类型匹配任意不包含斜杠的字符串int匹配正整数并且会自动转成 Python 的 int 类型float匹配浮点数自动转成 floatpath匹配任意字符串包括斜杠适合处理层级路径为什么转换器很重要我见过不少新手踩这样的坑写成post_id然后在代码里做判断app.route(/post/post_id) def show_post(post_id): if post_id 1: # post_id 是字符串 1永远不会等于整数 1 ...因为默认的 string 转换器把 URL 里的内容都当成字符串所以post_id拿到的实际上是1而不是1。与其在自己代码里做类型转换、判断不如直接在路由里用int:post_id让框架替你完成类型转换函数里直接就是一个整数。顺带提一下多个动态参数是可以叠加的比如app.route(/category/cate/post/int:post_id) def show_post(cate, post_id): return f分类{cate}文章编号{post_id}这时候 Flask 会按位置和名称同时解析多个参数。3.3 用 url_for 避免硬编码 URL当你的应用越来越大你会发现一个问题如果所有跳转都用字符串写死 URL比如redirect(/post/3)一旦路由规则修改所有引用这个 URL 的地方都要跟着改维护成本非常高。Flask 提供了一个非常优雅的解决方案url_for函数。它通过路由对应的函数名来反向生成 URLfrom flask import Flask, url_for, redirect app Flask(__name__) app.route(/) def dashboard(): return redirect(url_for(show_post, post_id3)) app.route(/post/int:post_id) def show_post(post_id): return f正在查看文章{post_id}url_for(show_post, post_id3)会根据函数名show_post找到对应的路由规则再拼接出完整的/post/3。这样即使你以后把路由路径从/post/int:post_id改成/article/int:post_id只要函数名不变这里的跳转逻辑就不用动。这不是 Flask 的特色功能而是 Web 框架的通用最佳实践。我的建议是从第一个应用开始就养成用url_for而不是硬编码 URL 的习惯这比后期再重构要省事得多。4. 调试模式与自动重载开发利器还是生产隐患4.1 debugTrue 带来的两个能力第一个应用运行起来之后你会遇到一个让人非常烦躁的问题每次改完代码都要重启服务才能看到效果。这大大降低了开发效率。Flask 的 debug 模式就是来解决这个问题的app.run(debugTrue)打开 debug 模式之后你获得了两个关键能力。第一个是自动重载当代码文件发生变化时开发服务器会自动检测到修改并重新启动刷新浏览器就能看到新效果不用手动重启。这是开发阶段最重要的体验优化之一。第二个是交互式调试器程序报错时浏览器页面会显示一段详细的堆栈信息并且把出错的位置用醒目的方式标出来。你可以直接在浏览器里看到 Exception 发生在哪个文件哪一行、相关变量的值是什么相当于网页版的控制台。展开说一下这个交互式调试器的厉害之处。如果代码报错普通的做法是看终端里的堆栈信息然后回到代码里排查。而 Flask 的 debugger 会在出错页面把每一个堆栈帧展开你点开某一帧就能看到当前上下文里的变量名和值甚至可以直接把鼠标放到某个变量上查看内容。省去了你在代码里加 print 或者 debugger 的功夫。当然这背后有一个 PIN 码验证机制打开 debugger 的时候浏览器会要求你输入终端里显示的 PIN 码这是为了防止远程攻击者直接利用这个工具搞破坏。4.2 为什么生产环境绝对不能开 debug很多新手学会debugTrue之后一直开着它甚至直接部署到服务器上。这绝对是一个严重点我必须在这里认真强调生产环境千万不要开 debug 模式。原因有几个。首先debug 模式下的自动重载会持续监听文件变化、消耗额外资源而且会自动重启服务。在服务器上这不是想要的行为。其次交互式调试器是一个巨大的安全后门。攻击者如果拿到了 PIN 码就能在服务器上执行任意 Python 代码。哪怕没有 PIN 码debug 页面本身也会泄露你的源码路径、依赖版本、环境变量等敏感信息这些信息对攻击者来说就是地图。那你可能会问生产环境怎么开服务答案是使用专门的生产级 WSGI 服务器。复杂一点的用 GunicornLinux/macOS、WaitressWindows 跨平台简单一点的可以先用pip install waitress waitress-serve --host0.0.0.0 --port8080 app:app注意这里的app:app是两个部分冒号前面是文件名app.py冒号后面是 Flask 实例的名字app Flask(__name__)。这个命令跑起来之后你访问服务器的 8080 端口得到的就是同一个 Flask 应用但它更稳定、性能更好、也更安全。我见过太多人把 Flask 自带的开发服务器直接用于线上结果并发一上来就卡住或者 debug 页面被扫描到服务器被入侵。这些教训都是真金白银换来的所以我情愿多啰嗦两句也要提醒你开发环境开 debug生产环境绝不开这是红线。5. 从单文件走向规范结构第一个应用的工程化思考5.1 一个常见的低级错误文件命名为 flask.py写第一个 Flask 应用时很多人会顺手把文件命名为flask.py然后运行起来发现报错翻了一夜 Stack Overflow 都找不到原因。问题的根源在于当你执行python flask.py时Python 会把当前目录加入模块搜索路径。接着你在代码里写from flask import FlaskPython 就在当前目录找到了flask.py把它当成要导入的模块了。结果这个文件里又没有Flask这个类自然就报 ImportError。更诡异的是有时候第一次运行还能通过是因为 Python 已经缓存了系统环境里的 Flask 模块但换一台机器或者清了缓存之后立刻暴露。这种问题一旦遇到排查起来极其恼火。提醒大家两个原则项目文件名不要和第三方库重名flask.py、requests.py、http.py这些名字都不要用。如果实在不小心创建了删除当前目录下的__pycache__缓存文件夹然后换个文件名。我到现在都记得自己第一次踩这个坑时的心情。明明敲的就是教程里的代码怎么一运行就报错。原因居然是文件名撞了库名谁能想到。5.2 最小可用项目结构第一个应用只要跑通就行单文件完全没问题。但当你开始写第二个、第三个应用时慢慢你会发现单文件越来越臃肿路由、配置、工具函数、数据库连接全都挤在一个文件里。这时候就需要做工程化的拆分。对于刚入门的阶段我建议一个最小可用的项目结构长这样my_flask_app/ ├── app.py # 应用入口路由注册 ├── config.py # 配置文件包含各种常量 ├── requirements.txt # 依赖列表 ├── static/ # 静态文件CSS、JS、图片 │ └── style.css ├── templates/ # HTML 模板 │ └── index.html └── venv/ # 虚拟环境目录不提交到版本库Flask 默认会从应用根目录下的static和templates文件夹里加载静态文件和模板。所以只要创建这两个文件夹并且在代码里用url_for(static, filenamestyle.css)引用静态文件用render_template(index.html)渲染模板就能以最少的配置支撑起一个像样的多页面应用。再往后你可以进一步把路由拆到 Blueprint蓝图里把模型和数据库操作拆到 models 文件里把公共函数拆到 utils 里。但这些都是后端工程化的进阶话题第一个应用阶段不用追求太多。先把单文件跑通再慢慢拆比一开始就套一堆目录结构要务实得多。5.3 requirements.txt让项目可复现写完第一个应用之后你应该顺手把当前虚拟环境里的依赖导出到requirements.txt这会让你的项目在另一台电脑上可以一键复现环境pip freeze requirements.txt这个文件里面记录了你当前环境所有依赖的精确版本号。换机器时只需要创建虚拟环境然后执行pip install -r requirements.txt就能把环境完整复制过来。别看这个动作小它是项目可以交付、可以协作的基础。如果没有这个文件你换电脑或者队友拉代码后只能一个个猜着装依赖装上来的版本还可能和你本地不一样然后莫名其妙多出一堆 bug。6. 常见问题与排查技巧从第一个应用开始积累经验6.1 端口被占用怎么办Flask 开发服务器默认跑在 5000 端口。如果你之前启动过服务没有关闭或者有其他程序占用了 5000启动时就会报OSError: [Errno 98] Address already in use或 Windows 下的WinError 10013。解决方式很简单要么换端口要么杀掉占用进程。换端口最直接app.run(debugTrue, port5001)或者不修改代码用环境变量方式flask run --port5001如果是端口被某个僵死的 Python 进程占用你可以用下面的命令找到并杀掉它。macOS / Linuxlsof -i :5000 kill -9 PIDWindowsnetstat -ano | findstr :5000 taskkill /F /PID PID这个操作在后面的开发中会用很多次建议现在的你就把它记住。6.2 修改代码后页面不刷新有的同学开了 debug 模式改完代码刷新浏览器发现页面还是老样子。这里要区分两种可能性。第一种是 Python 代码没有自动重载。这种情况常见于你用的 Flask 版本较旧或者 debug 没有正确开启。检查一下代码里是不是写了app.run(debugTrue)或者命令行启动时有没有加--debug参数。另外在 PyCharm 里运行 Flask它默认可能会走自己的配置你在终端里改了方案IDE 里不一定生效。第二种是前端静态资源被浏览器缓存了。这种情况刷新页面往往是 CSS 或 JS 没更新按CtrlF5强制刷新就能解决。如果想在开发阶段避免这种问题可以在引用静态文件的 URL 后面加一个动态参数比如?vxxx这样每次文件内容变化时URL 也随之变化浏览器就会当作新资源重新请求。6.3 常见问题速查表为了让各位少走弯路我把第一个 Flask 应用阶段最容易踩的坑整理成一个速查表方便日后遇到问题时快速对照。常见问题可能原因解决方案运行from flask import Flask报 ImportError没有安装 Flask或者装错 Python 环境确认在正确的虚拟环境中执行pip install flask访问 URL 返回 404路由路径和请求路径不一致或大小写不对核对 route 装饰器中的路径注意/about和/about/的区别修改代码后页面不更新debug 没开或浏览器缓存检查app.run(debugTrue)按 CtrlF5 强制刷新所有页面都报 500 Internal Server Error代码抛异常打开 debug 模式查看堆栈或在终端看报错端口被占用上一个服务没关干净换端口或使用 lsof / netstat 查杀占用的进程启动时提示找不到 Flask 应用用flask run时没有设置FLASK_APP环境变量执行export FLASK_APPapp.pyWindows 用 set排查问题时有个原则我想多强调一下先看终端输出再看浏览器页面。大部分 Flask 问题都会在终端里留下堆栈信息那里是排查的第一现场。浏览器上看到的报错页面虽然很花哨但往往信息不够全面。6.4 一个额外的排查技巧善用 print 与日志第一个应用阶段你不需要引入复杂的日志系统学会用 print 就够了。但我想分享一个很多新手容易忽略的做法在关键位置打印请求信息。from flask import Flask, request app Flask(__name__) app.route(/) def hello(): print(f收到请求路径{request.path}方法{request.method}) return Hello, Flask!request对象是 Flask 在接收到 HTTP 请求后自动创建的一个全局上下文对象本质上是一个代理。在视图函数里你可以直接通过它获取请求的 URL、方法、请求头、查询参数等一切信息。打印这些信息能帮你快速定位“为什么请求没有到达预期函数”之类的问题。实战中我经常在新增路由时先加一行print(request.url)确认请求真的打到了正确的地方然后再去写业务逻辑。别看这只是个土办法十个问题里有八个都能靠它排除掉大半可能性。写在最后的一些个人体会按照这套流程走下来你理论上已经拥有一个能跑、能响应多个 URL 的 Flask 应用了。虽然它还很简陋但这个“从零到一”的过程和 Web 框架的核心概念是相通的路由、视图、请求、响应、调试与部署后面所有复杂功能的意识起点都在这里。我第一次学 Flask 的时候犯过的错误比上面列的多得多文件命名撞库、忘记激活虚拟环境、开着调试模式上生产。这些坑不亲身踩一遍很难记住但踩完之后你会对框架的运行机制有更深的体感。所以我不建议你囫囵吞枣地往下赶进度把第一个应用多玩几遍加几个路由、尝试传参、看看调试页面的堆栈长什么样、故意写错一段代码再观察报错信息这些看似无聊的折腾恰恰是你把基础打扎实的最好方式。如果接下来你打算继续深入学习我的建议是按照这条线往下走搞懂 Jinja2 模板渲染尝试把 HTML 拆到模板里理解蓝图Blueprint如何组织模块学会连接数据库SQLite 起步就够了最后了解生产部署的方式。下一篇文章我计划专门讲模板渲染与静态文件处理毕竟一个只会返回字符串的 Flask 应用还不能叫真正的应用。到时候我们继续聊。
返回列表