【Flask 基础 ①】 | 路由、参数与模板渲染
0 序言
Flask 是 Python 生态中一款轻量级 Web 框架,以简洁、灵活著称。
学习 Flask 的意义在于:
- 快速开发:通过少量代码即可
搭建功能完整的 Web 应用
; - 理解原理:其设计清晰体现了 Web 框架的核心逻辑,如路由、请求处理、模板渲染等操作;
- 拓展性强:支持自定义扩展(如数据库、认证),适配不同场景。
本笔记围绕 Flask 核心功能
展开,结合代码示例,逐步拆解 Web 开发的关键环节。
1 Flask 应用初始化
创建 Flask 应用实例,是所有功能的起点。
如果当前你的python环境里没有flask这个包,就自行使用以下命令下载即可:
pip install flask
__name__
帮助 Flask 定位项目路径(如模板、静态文件的加载)。
运行以下程序:
from flask import Flask
app = Flask(__name__) # 初始化应用,__name__ 标识当前模块 if __name__ == '__main__': app.run() # 启动开发服务器,仅用于测试环境
注意事项:
app.run()
默认运行在127.0.0.1:5000
,生产环境需用 Gunicorn 等服务器;- 调试模式可通过
app.run(debug=True)
开启,便于开发时自动重启和报错调试。
运行结果如下:
接着我们需要将http://127.0.0.1:5000 这个结果复制到网页中打开。
当然,在VsCode中,你也可以直接ctrl+单击
即可打开。
但是在这里,当我们打开相关链接时,我们会发现:
这里的原因主要是:前面程序里没有定义任何路由(比如 /、/hello 等),所以访问任何 URL 都会报 404。
由于Flask的路由是 “显式定义” 的,只有通过 @app.route(…) 装饰器声明的 URL 路径,才能被服务器识别和处理。
当你访问http://127.0.0.1:5000/
时,Flask 会检查是否有函数绑定了 / 这个路径;
如果没有找到匹配的路由,就会返回 404 Not Found(服务器不知道如何处理这个请求)。
接下来,第2小结就来重点解决这个问题!
2.路由系统:URL与视图的映射
路由是 Web 应用的入口规则
:通过 @app.route
装饰器,将 URL 路径与 Python 函数绑定,处理客户端请求。
2.1 基础路由
定义最基本的 URL 映射,如首页 /
、功能页 /hello
。
- 示例:
from flask import Flask
app = Flask(__name__) # 定义路由
@app.route('/')
def index(): return 'Hello, World!' @app.route('/hello')
def hello(): return 'This is Hello Page' # 调试:打印所有注册的路由
print("当前注册的路由:")
print(app.url_map) # 关键!查看 Flask 到底注册了哪些路由 if __name__ == '__main__': app.run(debug=True)
运行程序,看看结果:
接着我们打开网址:
看到这里,我们再来对刚刚的程序进行逐一分析,重点是要理解它的运行原理:
第一步:导入Flask类并创建应用实例(初始化阶段)
from flask import Flask
app = Flask(__name__)
这一步创建了一个Flask应用实例,是整个Web应用的核心
__name__
参数的作用:
- 告诉Flask应用当前模块的名称
- 帮助Flask确定应用的根目录,用于查找模板和静态文件
- 当直接运行该脚本时,
__name__
的值为'__main__'
第二步:定义路由和视图函数(路由注册阶段)
@app.route('/')
def index(): return 'Hello, World!'
这里包含两个比较重要的部分,一个是路由装饰器,另一个则是视图函数。
路由装饰器@app.route('/')
:
-
这是Flask的核心机制,使用Python装饰器语法
-
作用是将URL路径
'/'
与下面的视图函数index()
绑定 -
当用户访问
http://127.0.0.1:5000/
时,Flask会调用index()
函数 -
视图函数
index()
:- 处理对应路由的业务逻辑
- 返回值会作为HTTP响应的内容发送给客户端
- 可以返回字符串、HTML、JSON等多种格式
第三步:打印路由信息(调试阶段)
print("当前注册的路由:")
print(app.url_map)
app.url_map
:- 是Flask内部维护的路由映射表
- 包含了所有已注册的URL规则和对应的视图函数
- 运行时会输出所有可用的路由信息,方便调试
第四步:启动开发服务器(运行阶段)
if __name__ == '__main__': app.run(debug=True)
-
if __name__ == '__main__':
:- 确保只有当直接运行该脚本时才启动服务器
- 当该脚本被作为模块导入时,不会执行服务器启动代码!!!
-
app.run(debug=True)
:- 启动Flask内置的开发服务器
debug=True
开启调试模式,具有以下特性:- 代码修改后自动重启服务器
- 出现错误时显示详细的调试信息
- 仅用于开发环境,生产环境必须关闭
当用户在浏览器中访问http://127.0.0.1:5000/hello
时,整个处理流程如下:
- 浏览器向Flask服务器发送HTTP GET请求
- Flask服务器接收请求,解析URL路径
/hello
- 在
app.url_map
中查找与/hello
匹配的路由规则 - 找到匹配的视图函数
hello()
并调用 - 执行
hello()
函数,获取返回值'This is Hello Page'
- Flask将返回值包装成HTTP响应,发送给浏览器
- 浏览器接收响应并显示内容
也就是说,这个简单的程序实例展示了Web框架的核心思想:将URL路径映射到处理函数,处理请求并返回响应。
2.2 请求方法与端点
methods
:限制请求方式(如仅允许GET
或POST
),保证接口安全;endpoint
:自定义路由的唯一标识
,避免视图函数名冲突。
@app.route('/hi', methods=['GET', 'POST'], endpoint='hi_page')
def hi(): # 若 endpoint 不指定,默认用函数名 `hi` 作为标识 return 'Hi!'
运行程序后结果如下:
这个程序跟上一小节的程序显示出来的结果都是一样的,
区别主要在哪里呢?
多了两个关键参数:methods
和 endpoint
methods=['GET', 'POST']
主要是用于限制允许的请求方式
- 默认情况:前面的
index()
、hello()
没写methods
,默认只允许GET
请求(浏览器地址栏访问、点击链接等都是GET
)。 - 这里的变化:
methods=['GET', 'POST']
表示这个路由既允许GET
请求,也允许POST
请求(如表单提交、API 数据提交等)。
打个比方:
- 用浏览器访问
http://127.0.0.1:5000/hi
(GET
请求)→ 正常返回Hi!
; - 用表单或代码向
http://127.0.0.1:5000/hi
提交数据(POST
请求)→ 也能正常处理(前面的路由会拒绝POST
请求)。
而这里的endpoint='hi_page'
则是自定义路由的唯一标识
- 默认情况:前面的
index()
、hello()
没写endpoint
,Flask会默认用函数名作为路由的唯一标识。 - 这里的变化:
endpoint='hi_page'
手动指定了标识,不再依赖函数名hi
。
作用:
- 避免函数名冲突:比如有两个函数名相同但路径不同的路由,可通过
endpoint
区分; - 方便 URL 反转:用
url_for('hi_page')
可以生成该路由的 URL,而不是依赖函数名url_for('hi')
。
举例:
from flask import url_for
# 生成 URL 时,用 endpoint 而不是函数名
print(url_for('hi_page')) # 输出 '/hi'(不管函数名叫什么)
也可以看下表,可能会更清晰直观。
特性 | 前面的 index() /hello() | 这里的 hi() |
---|---|---|
允许的请求方式 | 只允许 GET (默认) | 允许 GET 和 POST (显式指定) |
路由的唯一标识(endpoint) | 等于函数名(如 index ) | 自定义为 hi_page (与函数名无关) |
3. 路由参数(转换器)
前面定义的路由(如 /、/hello)都是静态路径,即每个 URL 对应固定的功能。但实际开发中,我们经常需要处理 动态变化的 URL:
举个例子:
社交平台的用户主页:/user/1(用户 ID=1)、/user/2(用户 ID=2);
电商平台的商品详情:/product/10086(商品 ID=10086);
博客的文章详情:/post/20250730(文章日期 = 20250730)。
如果为每个用户、商品、文章都单独写一个 @app.route,代码会变得极其冗余,要是有10 万个用户就要写 10 万个路由。
这时候,路由参数(转换器) 就能解决问题 —— 它允许我们在 URL 中定义 动态变量,让同一路由规则匹配多个相似的URL
,并将动态部分作为参数传递给视图函数。
接下来,我们就来学习 Flask 如何通过转换器实现这种灵活的动态路由。
3.1 内置转换器
Flask 提供默认转换器:string
(默认,非斜杠字符串)、int
(整数)、float
(浮点数)、path
(含斜杠的路径)。
@app.route('/user/<int:id>')
def user_detail(id): if id == 1: return 'Python' elif id == 2: return 'Django' return 'Hello'
我们对该程序进行分析,
程序核心是让 URL 能够包含动态变化的值并在视图函数中处理这些值。
@app.route('/user/<int:id>') # 关键:<int:id> 是路由参数(带转换器)
def user_detail(id): # 函数参数 id 接收 URL 中的动态值# 处理逻辑...
<int:id>
:这是 Flask 的路由参数语法,由两部分组成:
int
:转换器,限制 URL 中的参数必须是整数(如果传字符串,会直接报 404);id
:参数名,用于在视图函数中接收这个动态值(必须和函数参数名一致)。
实际运行效果
当用户访问不同的 URL 时,id
会动态变化,视图函数会根据 id
的值返回不同内容:
访问的 URL | id 的值 | 函数返回内容 |
---|---|---|
http://127.0.0.1:5000/user/1 | 1 | Python |
http://127.0.0.1:5000/user/2 | 2 | Django |
http://127.0.0.1:5000/user/3 | 3 | Hello |
http://127.0.0.1:5000/user/abc | 无效(非整数) | 404 错误 |
接着我们来看看实际运行效果:
当然,除了这一种内置的转换器外,还有一些其他的,不单单只是int类型,例如:
转换器 | 作用 | 示例 URL | 匹配后的值类型 |
---|---|---|---|
string | 默认值,匹配非斜杠的字符串(不含 / ) | /user/<string:name> | 字符串 |
int | 匹配整数 | /user/<int:id> | 整数 |
float | 匹配浮点数 | /price/<float:p> | 浮点数 |
path | 匹配含斜杠的路径(如文件路径) | /file/<path:f> | 字符串 |
3.2 自定义正则转换器
内置转换器(int、string 等)就已经能满足大部分简单场景,但实际开发中,我们常需要更精确的参数规则。比如:
验证手机号:必须是 11 位数字(int 只能保证是整数,无法限制长度为 11 位);
匹配日期:格式必须是 YYYY-MM-DD(string 无法验证这种特定结构);
校验邮箱:必须包含 @ 和域名(如 xxx@example.com)。
这些场景需要用正则表达式来定义精确规则,而内置转换器无法直接支持自定义正则。
因此,我们就需要通过自定义正则转换器,将正则规则与路由参数绑定,实现更灵活的参数校验。
接下来,我们就来学习如何实现自定义正则转换器,让路由参数的验证更灵活。
步骤:
① 继承 BaseConverter
,定义 regex
属性;
② 注册到 app.url_map.converters
;
③ 在路由中使用自定义转换器。
示例:
from flask import Flask
from werkzeug.routing import BaseConverter app = Flask(__name__) # 1. 定义自定义转换器
class RegexConverter(BaseConverter): def __init__(self, url_map, regex): super().__init__(url_map) self.regex = regex # 正则规则由路由传入 # 2. 注册转换器
app.url_map.converters['regex'] = RegexConverter # 3. 使用自定义转换器
@app.route('/index/<regex("\d{3,6}"):value>')
def index(value): print(value) # 匹配 3-6 位数字 return 'OK'
这里逐一分析:
第一步:导入必要的类
from flask import Flask
from werkzeug.routing import BaseConverter # 导入转换器基类
BaseConverter
是 Flask 路由转换器的基类,所有自定义转换器都必须继承它。
第二步:定义自定义转换器类
class RegexConverter(BaseConverter): def __init__(self, url_map, regex): super().__init__(url_map) # 调用父类初始化方法 self.regex = regex # 存储正则规则(由路由传入)
核心逻辑:通过 __init__
方法接收一个 regex
参数(正则表达式),并赋值给 self.regex
。
self.regex
是基类 BaseConverter
中用于匹配参数的关键属性,Flask 会自动用这个正则规则验证 URL 中的参数。
第三步:注册转换器到 Flask 应用
app.url_map.converters['regex'] = RegexConverter
将自定义的 RegexConverter
注册到 Flask 的路由系统中,并给它起了一个别名 'regex'
。
后续在路由中,就可以用 <regex(正则规则):参数名>
的格式使用这个转换器。
第四步:在路由中使用自定义转换器
@app.route('/index/<regex("\d{3,6}"):value>')
def index(value): print(value) # 打印匹配到的参数(如 1234) return 'OK'
<regex("\d{3,6}"):value>
是使用格式:
regex
:我们注册的转换器别名;"\d{3,6}"
:传给转换器的正则规则(表示“3-6 位数字”);value
:参数名,用于在视图函数中接收匹配到的值。
接下来运行一下,实际运行效果:
访问不同的 URL 时,Flask 会用正则 \d{3,6}
验证参数:
访问的 URL | 是否匹配 | 视图函数接收的 value | 页面返回 |
---|---|---|---|
/index/123 | 匹配(3位数字) | '123' | OK |
/index/123456 | 匹配(6位数字) | '123456' | OK |
/index/12 | 不匹配(2位数字) | 无(直接返回 404) | 404 错误 |
/index/abc | 不匹配(非数字) | 无(直接返回 404) | 404 错误 |
4 模板渲染:分离逻辑与展示
前面的视图函数都是通过 return 直接返回字符串(如 return ‘Hello, World!’),但实际的 Web 页面往往包含大量 HTML、CSS、JavaScript 代码(比如带样式的列表、表单、动态内容)。
如果继续在 Python 代码里硬写 HTML,会出现很多问题,比如说:
维护困难:比如要修改页面样式(如字体颜色),需要在 Python 代码中找对应的字符串修改,逻辑和展示混在一起;
可读性差:复杂的 HTML 结构(如嵌套的 div、表格)写在 Python 字符串里,格式混乱,容易出错。
为了解决这些问题,我们可以用Flask引入模板渲染机制,即把页面的 HTML 代码放在专门的 templates 文件夹中,视图函数只负责处理业务逻辑并传递数据,再通过模板引擎将数据动态插入 HTML 中。
接下来,我们就来学习如何使用 Flask 的模板渲染功能。
from flask import Flask, render_template app = Flask(__name__) @app.route('/index')
def index(): # 渲染 templates/index.html 文件 return render_template('index.html')
对程序进行分析,
第一步:导入模板渲染函数
from flask import Flask, render_template # 新增 render_template
render_template
是 Flask 提供的模板渲染函数,作用是:
- 找到并加载
templates
文件夹中的 HTML 文件; - 可以向 HTML 文件传递动态数据(后续扩展);
- 最终返回渲染后的完整 HTML 内容给浏览器。
第二步:定义路由与视图函数
@app.route('/index')
def index(): # 渲染 templates/index.html 文件 return render_template('index.html')
- 当用户访问
http://127.0.0.1:5000/index
时,Flask 会执行index()
函数; render_template('index.html')
会自动到项目的templates
文件夹中寻找index.html
文件(必须提前创建这两个)。
第三步:项目结构要求
使用模板渲染时,必须遵守 Flask 的默认目录结构:
你的项目文件夹/
├── test.py (当前 Python 代码文件)
└── templates/ (存放 HTML 模板的文件夹,必须叫这个名字) └── index.html (被渲染的 HTML 文件)
如果 templates
文件夹不存在,或 index.html
不在该文件夹中,会报 TemplateNotFound
错误。
我下面演示一下:
然后我们现在运行一下,
这里显示空白的原因就是刚刚只是创建了一个html文件,该文件并没有写内容,所以是空白的。
接着我们在html文件里面写进内容:
<!DOCTYPE html>
<html>
<head><title>测试页面</title>
</head>
<body><h1>这是模板渲染的页面!</h1><p>用户您好,如果您能看到这句话,说明模板加载成功 ✌️</p>
</body>
</html>
这里简单对程序分析一下:
<!DOCTYPE html> <!-- 1. 声明文档类型:HTML5 -->
<html> <!-- 2. 根元素:所有 HTML 内容的容器 -->
<head> <!-- 3. 头部:存放元数据(不直接显示,用于配置页面) --><title>测试页面</title> <!-- 页面标题,显示在浏览器标签上 -->
</head>
<body> <!-- 4. 主体:存放页面的可见内容 --><h1>这是模板渲染的页面!</h1> <!-- 大标题,突出显示 --><p>用户您好,如果您能看到这句话,说明模板加载成功 ✌️</p> <!-- 段落文本,解释信息 -->
</body>
</html>
重新运行下看看结果:
这里理解清楚后,后续我们就可以针对该网页进行一些复杂的设计的时候,就可以先把它放到html文件里,然后在py文件调用就可以了。
程序的实际运行流程是这样的:
- 用户访问
http://127.0.0.1:5000/index
; - Flask 匹配到
/index
路由,调用index()
函数; render_template('index.html')
执行:- 查找
templates/index.html
文件; - 读取该文件的 HTML 内容;
- 将 HTML 内容作为响应返回给浏览器;
- 查找
- 浏览器解析 HTML 并显示页面。
相比于直接返回字符串,这种方法有以下3种优势:
- 优势1:HTML 结构更清晰:复杂的页面布局(如 CSS 样式、嵌套标签)写在
.html
文件中,比写在 Python 字符串里更易读、易修改; - 优势2:前后端分离:在一些细分前后端开发的项目中,后端开发者只需关注
test.py
中的逻辑,前端开发者只需修改index.html
的样式,分工更明确; - 优势3:支持动态数据(后续扩展):可以在
render_template
中传递参数(如render_template('index.html', name='Python')
),在 HTML 中用{{ name }}
显示,实现动态内容。
5 请求处理:request 对象
前面的模板渲染让页面能展示内容
,但 Web 应用的核心是**“和用户互动”**:
比如:用户在登录页提交账号密码、用户在搜索框输入关键词、用户上传头像或文件等等
这些场景都需要获取用户的输入数据
,但如何把浏览器提交的数据传递到 Flask 代码里?
这时候,request 对象 就成了核心工具。
接下来,我们通过一个登录功能的示例,学习如何用 request 处理用户的 GET/POST 请求,实现 页面展示 + 数据交互
的完整流程。
from flask import Flask, render_template, request app = Flask(__name__) @app.route('/login', methods=['GET', 'POST'])
def login(): if request.method == 'GET': # 处理 GET 请求:渲染登录页面 return render_template('login.html') elif request.method == 'POST': # 处理 POST 请求:获取表单数据 username = request.form.get('username') password = request.form.get('password') print(f'用户名:{username},密码:{password}') return '登录成功!'
老样子,先对程序进行分析:
这个程序实现了一个简单的登录功能:
- 当用户通过 GET 请求 访问
/login
时,显示登录页面(login.html
); - 当用户在登录页面提交表单(POST 请求)时,Flask 接收表单中的用户名和密码,并返回
登录成功
。
第一步:导入核心工具
from flask import Flask, render_template, request # 新增 request 对象
request
是 Flask 中处理请求的核心对象,封装了客户端发送的所有数据(如请求方式、表单内容、URL 参数等)。
第二步:定义支持 GET/POST 的路由
@app.route('/login', methods=['GET', 'POST']) # 允许两种请求方式
def login(): # 根据请求方式处理不同逻辑
methods=['GET', 'POST']
表示该路由同时支持 GET 和 POST 两种请求方式:
- GET:通常用于“获取资源”(如访问页面、查询数据);
- POST:通常用于“提交数据”(如表单提交、上传文件)。
(这个部分前面说过了,这里不再赘述)
第三步:处理 GET 请求:展示登录页面
if request.method == 'GET': # 处理 GET 请求:渲染登录页面 return render_template('login.html')
- 当用户在浏览器地址栏输入
http://127.0.0.1:5000/login
时,发送的是 GET 请求; request.method == 'GET'
条件成立,Flask 渲染templates/login.html
(登录页面),让用户可以输入账号密码。
第四步:处理 POST 请求:接收表单数据
elif request.method == 'POST': # 处理 POST 请求:获取表单数据 username = request.form.get('username') # 获取表单中 name="username" 的值 password = request.form.get('password') # 获取表单中 name="password" 的值 print(f'用户名:{username},密码:{password}') # 在终端打印数据 return '登录成功!'
- 当用户在登录页面点击“提交”按钮时,表单会以 POST 请求 提交到
/login
; request.method == 'POST'
条件成立,通过request.form.get('字段名')
提取表单数据:- 假设
login.html
中有<input name="username">
,则request.form.get('username')
会获取用户输入的用户名; - 同理,
request.form.get('password')
获取密码。
- 假设
要让这个程序正常运行,templates
文件夹中需要有 login.html
(登录表单页面),内容大致如下:
<!DOCTYPE html>
<html>
<body><form method="POST"> <!-- 表单提交方式为 POST --><label>用户名:</label><input type="text" name="username"><br> <!-- name 必须为 "username" --><label>密码:</label><input type="password" name="password"><br> <!-- name 必须为 "password" --><button type="submit">登录</button></form>
</body>
</html>
- 表单的
method="POST"
必须与 Flask 路由允许的方式一致; - 输入框的
name
属性(username
、password
)必须与request.form.get('xxx')
中的参数一致,否则无法获取数据。
完整交互流程如下:
- 用户访问
http://127.0.0.1:5000/login
(GET 请求)→ Flask 返回登录页面; - 用户在页面输入用户名和密码,点击“登录”→ 表单以 POST 请求 提交到
/login
; - Flask 检测到 POST 请求,通过
request.form
获取表单数据 → 终端打印用户名和密码; - Flask 返回
登录成功!
→ 浏览器显示成功信息。
运行下程序,结果如下:
通过这一小节,我们也能明白request
对象的核心作用,
request
是连接用户输入
和后端逻辑
的桥梁,除了 form
(表单数据),还常用这些属性:
request.args
:获取 URL 中的查询参数(如?page=1&size=10
);request.files
:获取上传的文件(如头像图片);request.headers
:获取请求头信息(如浏览器类型、Cookie)。
这是 Web 应用中用户交互
的基础模式,无论是登录、注册还是表单提交,都遵循类似的逻辑。
6 重定向与 URL 构建
在完成用户交互(如表单提交)后,我们常常需要 “引导用户前往新页面”:
例如:用户登录成功后,从登录页跳转到首页;用户注册成功后,从注册页跳转到个人中心;
用户访问了不存在的页面时,自动跳转到 404 提示页。
这种页面跳转
的需求,就需要用重定向(redirect) 来实现。
同时,在跳转或页面链接中,我们不可避免要写 URL。但如果直接硬编码 URL,会带来一个问题:一旦路由规则修改(比如 /home 改成 /index),所有用到这个 URL 的地方都要手动修改,非常繁琐。
为了解决这个问题,Flask 提供了 url_for 函数,它能通过视图函数名
动态生成对应的 URL
也就是路由修改时,生成的 URL 会自动更新。
接下来,我们就来学习如何用 redirect 实现页面跳转,以及如何用 url_for 动态构建 URL,让程序更灵活、易维护。
from flask import Flask, redirect, url_for app = Flask(__name__) @app.route('/index')
def index(): # 重定向到 hello 视图(通过 url_for 生成 URL) return redirect(url_for('hello')) @app.route('/hello')
def hello(): return 'Welcome to Hello Page!'
结果如下:
页面下面也会显示hello视图函数的输出内容。
7 总结
Flask 的核心设计围绕简洁灵活展开:
- 路由是请求的入口,支持动态参数和自定义规则;
- request 统一处理输入,适配不同请求方式;
- 模板分离展示逻辑,提升可维护性;
- 重定向与 url_for 实现页面跳转和 URL 解耦。
学习 Flask 的过程,是逐步理解 Web 应用请求-处理-响应
流程的过程。
后续可结合数据库、等扩展,搭建更复杂的项目(如博客、API 服务)。
建议多实践,剩下的内容会在下文介绍,从简单页面到完整功能,逐步深化对 Web 开发的理解。