Skip to main content

Flask

1. 什么是Flask?

Flask 是一个用 Python 编写的轻量级 Web 应用框架。它被设计为易于使用、灵活且可扩展,是构建 Web 应用程序和 API 的绝佳选择,尤其适合初学者和小型到中型项目。

1.1 生活中的类比

想象一下,你想建一个网站,比如个人博客或者在线商店。

传统方法

  • 需要学习复杂的Web服务器配置
  • 需要处理HTTP协议的细节
  • 需要自己实现路由、模板等功能
  • 开发效率低,容易出错

使用Flask

  • 几行代码就能创建一个Web应用
  • Flask帮你处理了底层的复杂性
  • 专注于业务逻辑,而不是技术细节
  • 开发效率高,代码简洁

Flask 就像是一个"工具箱",提供了构建Web应用所需的基本工具,让你可以快速搭建网站。

1.2 Flask的核心定位

Flask 的定位是:一个轻量级、灵活、易扩展的Web应用框架

核心特点

  • 微框架 :核心简单,只提供最基本的功能
  • 高度可扩展 :通过扩展库添加更多功能
  • 灵活自由 :没有强制的项目结构
  • 易于学习 :对初学者非常友好

1.3 为什么选择Flask?

vs 传统Web开发

  • 传统方式需要处理大量底层细节,Flask封装了这些复杂性
  • 传统方式开发效率低,Flask可以快速开发
  • 传统方式代码冗长,Flask代码简洁优雅

2. Flask核心概念

2.1 应用对象(Flask)

Flask应用的核心是应用对象,它是整个Web应用的入口。

创建应用对象

# 导入Flask类
# Flask类用于创建Web应用实例
from flask import Flask

# 创建Flask应用实例
# __name__用于确定应用的根路径,Flask会根据它找到模板和静态文件
app = Flask(__name__)

# 打印应用对象信息(用于验证)
print(f"Flask应用已创建: {app}")
print(f"应用名称: {app.name}")

应用对象的作用

  • 配置路由(URL到函数的映射)
  • 注册扩展(如数据库、表单等)
  • 管理应用配置
  • 处理请求和响应

2.2 路由(Route)

路由是将URL映射到Python函数的过程。当用户访问某个URL时,Flask会调用对应的函数。

基本路由

# 导入Flask类
from flask import Flask

# 创建Flask应用实例
app = Flask(__name__)

# 定义根路由
# @app.route('/')表示当用户访问网站根目录时
# 装饰器@app.route()将URL '/' 映射到下面的函数
@app.route('/')
def index():
# 返回响应内容(HTML字符串)
return '<h1>欢迎访问首页!</h1>'

# 定义另一个路由
# '/about'表示访问 http://localhost:5000/about 时
@app.route('/about')
def about():
# 返回关于页面的内容
return '<h1>关于我们</h1><p>这是一个Flask应用示例。</p>'

# 运行应用
if __name__ == '__main__':
# 启动开发服务器
# host='0.0.0.0'表示监听所有网络接口
# port=5000表示使用5000端口
# debug=True开启调试模式
app.run(host='0.0.0.0', port=5000, debug=True)

动态路由

# 导入Flask类
from flask import Flask

# 创建Flask应用实例
app = Flask(__name__)

# 定义动态路由
# <name>是URL参数,会被传递给函数
# 访问 /user/张三 时,name='张三'
@app.route('/user/<name>')
def show_user(name):
# 使用URL参数生成响应
return f'<h1>你好,{name}!</h1>'

# 定义带类型的动态路由
# <int:post_id>表示post_id必须是整数
# 访问 /post/123 时,post_id=123(整数)
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 使用整数参数
return f'<h1>文章 #{post_id}</h1>'

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

2.3 视图函数

视图函数是处理请求的核心,它接收请求,处理业务逻辑,然后返回响应。

视图函数的特点

  • 必须返回一个响应(字符串、HTML、JSON等)
  • 可以访问请求数据(如表单数据、URL参数等)
  • 可以调用其他函数或访问数据库

示例

# 导入Flask类和request对象
# request对象包含客户端发送的请求信息
from flask import Flask, request

# 创建Flask应用实例
app = Flask(__name__)

# 定义视图函数
# 这个函数处理根路径的请求
@app.route('/')
def index():
# 返回简单的HTML响应
return '<h1>首页</h1><p>欢迎访问!</p>'

# 定义带参数的视图函数
# 从URL中获取参数
@app.route('/greet/<name>')
def greet(name):
# 使用URL参数生成响应
return f'<h1>你好,{name}!</h1>'

# 定义处理GET和POST请求的视图函数
# methods参数指定允许的HTTP方法
@app.route('/form', methods=['GET', 'POST'])
def handle_form():
# 判断请求方法
if request.method == 'POST':
# 如果是POST请求,获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
return f'<h1>收到表单数据:{username}</h1>'
else:
# 如果是GET请求,返回表单页面
return '''
<form method="post">
<label>用户名:</label>
<input type="text" name="username">
<button type="submit">提交</button>
</form>
'''

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

2.4 请求对象(request)

request对象包含客户端发送的所有请求信息,如表单数据、URL参数、请求头等。

获取请求数据

# 导入Flask类和request对象
from flask import Flask, request

# 创建Flask应用实例
app = Flask(__name__)

# 定义处理表单的路由
# methods=['GET', 'POST']表示支持GET和POST两种请求方法
@app.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
password = request.form.get('password', '')

# 简单的验证逻辑(实际应用中应该连接数据库)
if username == 'admin' and password == '123456':
return f'<h1>登录成功!</h1><p>欢迎,{username}!</p>'
else:
return '<h1>登录失败</h1><p>用户名或密码错误</p>'
else:
# GET请求,显示登录表单
return '''
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post">
<label>用户名:</label><br>
<input type="text" name="username" required><br><br>
<label>密码:</label><br>
<input type="password" name="password" required><br><br>
<button type="submit">登录</button>
</form>
</body>
</html>
'''

# 定义获取URL参数的路由
# 访问 /search?q=Flask 时,q='Flask'
@app.route('/search')
def search():
# 获取URL参数
# request.args是包含URL参数的字典
query = request.args.get('q', '')
if query:
return f'<h1>搜索结果</h1><p>搜索关键词:{query}</p>'
else:
return '<h1>请输入搜索关键词</h1>'

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

2.5 模板渲染(render_template)

直接在Python代码中拼接HTML字符串很繁琐,Flask使用Jinja2模板引擎来分离Python代码和HTML。

为什么需要模板?

  • HTML代码和Python代码分离,更易维护
  • 可以复用HTML结构(如导航栏、页脚)
  • 支持变量、循环、条件判断等

使用模板

首先创建模板文件 templates/hello.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>问候页面</title>
</head>
<body>
<h1>你好,{{ name }}!</h1>
<p>欢迎访问我们的网站。</p>
</body>
</html>

然后使用模板:

# 导入Flask类和render_template函数
# render_template用于渲染HTML模板
from flask import Flask, render_template

# 创建Flask应用实例
app = Flask(__name__)

# 定义路由,使用模板渲染
# <name>是URL参数
@app.route('/hello/<name>')
def hello(name):
# 渲染模板
# 'hello.html'是模板文件名(在templates目录下)
# name=name将Python变量传递给模板
return render_template('hello.html', name=name)

# 定义带列表数据的路由
@app.route('/users')
def show_users():
# 定义用户列表
users = ['张三', '李四', '王五']
# 将用户列表传递给模板
return render_template('users.html', users=users)

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

创建 templates/users.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<ul>
{% for user in users %}
<li>{{ user }}</li>
{% endfor %}
</ul>
</body>
</html>

3. 博客系统

让我们创建一个完整的博客系统示例,包含首页、文章列表和文章详情页。

3.1 项目结构

首先创建项目目录结构:

Windows 系统:

# 创建项目目录
mkdir my_blog
cd my_blog

# 创建templates目录
mkdir templates

macOS 系统:

# 创建项目目录
mkdir my_blog
cd my_blog

# 创建templates目录
mkdir templates

项目结构:

my_blog/
│ app.py # 主应用文件

└───templates/ # HTML模板目录
base.html # 基础模板
index.html # 首页模板
post.html # 文章详情页模板

3.2 创建主应用文件

创建 app.py

# 导入Flask类和render_template函数
# Flask用于创建Web应用
# render_template用于渲染HTML模板
from flask import Flask, render_template

# 创建Flask应用实例
# __name__用于确定应用的根路径
app = Flask(__name__)

# 模拟博客文章数据
# 在实际应用中,这些数据通常来自数据库
posts = [
{
'id': 1, # 文章ID
'title': '第一篇文章', # 文章标题
'content': '这是我的第一篇博客文章内容。Flask是一个很棒的Web框架!', # 文章内容
'author': '小明', # 作者
'date': '2024-01-01' # 发布日期
},
{
'id': 2,
'title': '学习Flask',
'content': '今天学习了Flask的路由和模板,非常有趣!Flask让Web开发变得简单。',
'author': '小红',
'date': '2024-01-02'
},
{
'id': 3,
'title': 'Flask模板系统',
'content': 'Jinja2模板引擎非常强大,支持变量、循环、条件判断等功能。',
'author': '小刚',
'date': '2024-01-03'
}
]

# 定义首页路由
# '/'表示网站根目录
@app.route('/')
def index():
# 渲染首页模板
# 'index.html'是模板文件名
# posts=posts将文章列表传递给模板
return render_template('index.html', posts=posts)

# 定义文章详情页路由
# <int:post_id>表示URL参数,必须是整数
# 例如:访问 /post/1 时,post_id=1
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
# next()函数用于从迭代器中获取第一个匹配的元素
# 如果没找到,返回None
post = next((post for post in posts if post['id'] == post_id), None)

# 如果文章不存在,返回404错误
if post is None:
# 返回错误信息和状态码
return '<h1>文章未找到</h1><p>抱歉,您访问的文章不存在。</p>', 404

# 渲染文章详情页模板
# 'post.html'是模板文件名
# post=post将文章对象传递给模板
return render_template('post.html', post=post)

# 运行应用
if __name__ == '__main__':
# 启动开发服务器
# debug=True开启调试模式
app.run(debug=True)

3.3 创建基础模板

创建 templates/base.html(基础模板,定义公共结构):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 页面标题,使用Jinja2的block语法,子模板可以覆盖 -->
<title>{% block title %}我的迷你博客{% endblock %}</title>
<style>
/* 简单的CSS样式 */
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
header {
background-color: #4CAF50;
color: white;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
header a {
color: white;
text-decoration: none;
}
main {
padding: 20px;
}
article {
margin-bottom: 30px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
article h3 {
margin-top: 0;
}
article a {
color: #4CAF50;
text-decoration: none;
}
article a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<!-- 页头 -->
<header>
<!-- url_for()函数用于生成URL,'index'是视图函数名 -->
<h1><a href="{{ url_for('index') }}">我的博客</a></h1>
</header>

<!-- 主要内容区域 -->
<!-- block content是内容块,子模板可以填充内容 -->
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>

3.4 创建首页模板

创建 templates/index.html(首页模板,继承自base.html):

<!-- 继承基础模板 -->
<!-- extends指令表示此模板继承自base.html -->
{% extends "base.html" %}

<!-- 覆盖title块,设置页面标题 -->
{% block title %}首页 - 我的迷你博客{% endblock %}

<!-- 填充content块,定义页面主要内容 -->
{% block content %}
<h2>所有文章</h2>

<!-- 使用for循环遍历文章列表 -->
<!-- posts是Python传递过来的变量 -->
{% for post in posts %}
<article>
<!-- 文章标题,链接到文章详情页 -->
<!-- url_for()生成URL,'show_post'是视图函数名,post_id是参数 -->
<h3><a href="{{ url_for('show_post', post_id=post.id) }}">{{ post.title }}</a></h3>

<!-- 显示作者和日期 -->
<p><strong>作者:</strong>{{ post.author }} | <strong>日期:</strong>{{ post.date }}</p>

<!-- 显示文章内容的前100个字符 -->
<!-- [:100]是Python的切片语法,获取前100个字符 -->
<p>{{ post.content[:100] }}...</p>
</article>

<!-- 分隔线 -->
<hr>
{% endfor %}

<!-- 如果没有文章,显示提示信息 -->
{% if not posts %}
<p>暂无文章。</p>
{% endif %}
{% endblock %}

3.5 创建文章详情页模板

创建 templates/post.html(文章详情页模板):

<!-- 继承基础模板 -->
{% extends "base.html" %}

<!-- 设置页面标题为文章标题 -->
{% block title %}{{ post.title }} - 我的迷你博客{% endblock %}

<!-- 定义页面内容 -->
{% block content %}
<article>
<!-- 显示文章标题 -->
<h2>{{ post.title }}</h2>

<!-- 显示作者和日期 -->
<p><strong>作者:</strong>{{ post.author }} | <strong>日期:</strong>{{ post.date }}</p>

<!-- 显示完整文章内容 -->
<div>
{{ post.content }}
</div>
</article>

<!-- 返回首页的链接 -->
<a href="{{ url_for('index') }}">← 返回首页</a>
{% endblock %}

3.6 运行博客应用

Windows 系统:

# 在项目目录下运行
python app.py

macOS 系统:

# 在项目目录下运行
python3 app.py

运行后,访问以下URL:

  • http://127.0.0.1:5000/ - 首页,显示所有文章
  • http://127.0.0.1:5000/post/1 - 第一篇文章详情
  • http://127.0.0.1:5000/post/2 - 第二篇文章详情

4. 处理表单数据

Web应用经常需要处理用户提交的表单数据。Flask可以轻松处理GET和POST请求。

4.1 简单表单示例

创建一个处理用户注册表单的示例:

# 导入Flask类和request对象
# Flask用于创建Web应用
# request对象包含客户端发送的请求信息
from flask import Flask, request, render_template

# 创建Flask应用实例
app = Flask(__name__)

# 定义注册页面路由
# methods=['GET', 'POST']表示支持GET和POST两种请求方法
@app.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 如果是POST请求,处理表单提交
# 获取表单数据
# request.form是包含表单数据的字典
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')

# 简单的验证(实际应用中应该更严格)
if not username or not email or not password:
# 如果必填字段为空,返回错误信息
return '<h1>注册失败</h1><p>请填写所有必填字段。</p><a href="/register">返回</a>'

# 在实际应用中,这里应该将数据保存到数据库
# 这里只是简单返回成功信息
return f'''
<h1>注册成功!</h1>
<p>用户名:{username}</p>
<p>邮箱:{email}</p>
<p><a href="/register">继续注册</a></p>
'''
else:
# GET请求,显示注册表单
# 渲染注册表单模板
return render_template('register.html')

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

创建 templates/register.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>用户注册</h1>
<!-- 表单,method="post"表示使用POST方法提交 -->
<form method="post">
<!-- 用户名输入框 -->
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>

<!-- 邮箱输入框 -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>

<!-- 密码输入框 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>

<!-- 提交按钮 -->
<button type="submit">注册</button>
</form>
</body>
</html>

4.2 使用模板处理表单

更好的方式是在模板中处理表单,这样代码更清晰:

# 导入Flask类和request、render_template函数
from flask import Flask, request, render_template

# 创建Flask应用实例
app = Flask(__name__)

# 定义注册页面路由
@app.route('/register', methods=['GET', 'POST'])
def register():
# 初始化错误信息字典
errors = {}
# 初始化成功标志
success = False

# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
password = request.form.get('password', '')

# 验证用户名
if not username:
errors['username'] = '用户名不能为空'
elif len(username) < 3:
errors['username'] = '用户名至少需要3个字符'

# 验证邮箱
if not email:
errors['email'] = '邮箱不能为空'
elif '@' not in email:
errors['email'] = '邮箱格式不正确'

# 验证密码
if not password:
errors['password'] = '密码不能为空'
elif len(password) < 6:
errors['password'] = '密码至少需要6个字符'

# 如果没有错误,注册成功
if not errors:
success = True
# 在实际应用中,这里应该将数据保存到数据库
# 这里只是简单标记为成功

# 渲染模板,传递错误信息和表单数据
return render_template('register.html',
errors=errors,
success=success,
username=username,
email=email)
else:
# GET请求,显示空表单
return render_template('register.html', errors={}, success=False)

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

更新 templates/register.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 5px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
.error {
color: red;
font-size: 12px;
margin-bottom: 15px;
}
.success {
color: green;
background-color: #d4edda;
padding: 10px;
border-radius: 3px;
margin-bottom: 15px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>用户注册</h1>

<!-- 如果注册成功,显示成功消息 -->
{% if success %}
<div class="success">
<p>注册成功!</p>
</div>
{% endif %}

<!-- 注册表单 -->
<form method="post">
<!-- 用户名输入框 -->
<label for="username">用户名:</label>
<!-- value属性保留用户输入的值 -->
<input type="text" id="username" name="username"
value="{{ username if username else '' }}" required>
<!-- 如果有错误,显示错误信息 -->
{% if errors.username %}
<div class="error">{{ errors.username }}</div>
{% endif %}

<!-- 邮箱输入框 -->
<label for="email">邮箱:</label>
<input type="email" id="email" name="email"
value="{{ email if email else '' }}" required>
{% if errors.email %}
<div class="error">{{ errors.email }}</div>
{% endif %}

<!-- 密码输入框 -->
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
{% if errors.password %}
<div class="error">{{ errors.password }}</div>
{% endif %}

<!-- 提交按钮 -->
<button type="submit">注册</button>
</form>
</body>
</html>

5. 返回JSON数据(API)

Flask不仅可以返回HTML,还可以返回JSON数据,用于构建API。

5.1 简单的API示例

# 导入Flask类和jsonify函数
# Flask用于创建Web应用
# jsonify用于将Python字典转换为JSON响应
from flask import Flask, jsonify

# 创建Flask应用实例
app = Flask(__name__)

# 模拟用户数据(实际应用中来自数据库)
users = [
{'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'},
{'id': 2, 'name': '李四', 'email': 'lisi@example.com'},
{'id': 3, 'name': '王五', 'email': 'wangwu@example.com'},
]

# 定义获取所有用户的API
# 返回JSON格式的数据
@app.route('/api/users', methods=['GET'])
def get_users():
# 使用jsonify将Python字典列表转换为JSON响应
# jsonify会自动设置Content-Type为application/json
return jsonify(users)

# 定义获取单个用户的API
# <int:user_id>是URL参数,必须是整数
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
# 根据ID查找用户
user = next((u for u in users if u['id'] == user_id), None)

# 如果用户不存在,返回404错误
if user is None:
# jsonify也可以返回错误信息
return jsonify({'error': '用户未找到'}), 404

# 返回用户信息
return jsonify(user)

# 定义创建用户的API
# 使用POST方法创建新资源
@app.route('/api/users', methods=['POST'])
def create_user():
# 从请求中获取JSON数据
# request.json包含请求体中的JSON数据
from flask import request
data = request.json

# 简单的验证
if not data or 'name' not in data or 'email' not in data:
return jsonify({'error': '缺少必填字段'}), 400

# 创建新用户(实际应用中应该保存到数据库)
new_user = {
'id': len(users) + 1,
'name': data['name'],
'email': data['email']
}
users.append(new_user)

# 返回新创建的用户信息
return jsonify(new_user), 201 # 201表示资源创建成功

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

5.2 测试API

可以使用Python的requests库测试API:

# 导入requests库用于发送HTTP请求
import requests

# 定义API的基础URL
base_url = 'http://127.0.0.1:5000/api'

# 测试1:获取所有用户
print("测试1:获取所有用户")
response = requests.get(f'{base_url}/users')
# 打印响应状态码
print(f"状态码: {response.status_code}")
# 打印响应内容(JSON格式)
print(f"响应: {response.json()}")

# 测试2:获取单个用户
print("\n测试2:获取用户ID为1的用户")
response = requests.get(f'{base_url}/users/1')
print(f"状态码: {response.status_code}")
print(f"响应: {response.json()}")

# 测试3:创建新用户
print("\n测试3:创建新用户")
new_user = {
'name': '赵六',
'email': 'zhaoliu@example.com'
}
# 发送POST请求,传递JSON数据
response = requests.post(f'{base_url}/users', json=new_user)
print(f"状态码: {response.status_code}")
print(f"响应: {response.json()}")

6. 组织大型应用:Blueprint

6.1 什么是Blueprint?

当Flask应用变得越来越大时,把所有路由都写在一个文件中会变得难以维护。Blueprint(蓝图)是Flask提供的组织大型应用的方式,它可以将应用分解成多个模块,每个模块负责不同的功能。

生活中的类比

  • 没有Blueprint :就像把所有东西都放在一个大箱子里,找东西很困难
  • 使用Blueprint :就像把东西分类放在不同的抽屉里,每个抽屉有标签,找东西很容易

Blueprint的优势

  • 模块化 :将不同功能分离到不同模块
  • 可复用 :可以在多个应用中复用Blueprint
  • 易维护 :代码结构清晰,易于理解和维护
  • 团队协作 :不同开发者可以负责不同的Blueprint

6.2 为什么需要Blueprint?

问题场景 : 假设你有一个包含以下功能的Web应用:

  • 用户管理(注册、登录、个人资料)
  • 博客功能(文章列表、文章详情、发布文章)
  • 评论功能(发表评论、删除评论)

如果所有路由都写在一个文件中:

# 不好的做法:所有路由在一个文件中
from flask import Flask

app = Flask(__name__)

# 用户相关路由
@app.route('/user/register')
def register():
pass

@app.route('/user/login')
def login():
pass

# 博客相关路由
@app.route('/blog')
def blog_list():
pass

@app.route('/blog/<id>')
def blog_detail(id):
pass

# 评论相关路由
@app.route('/comment/add')
def add_comment():
pass

# ... 更多路由
# 文件会变得很长,难以维护

使用Blueprint的解决方案 : 将不同功能分离到不同的Blueprint中,代码更清晰、更易维护。

6.3 创建第一个Blueprint

让我们创建一个简单的Blueprint示例:

项目结构

my_app/
│ app.py # 主应用文件

├───blueprints/ # Blueprint目录
│ ├──__init__.py # 包初始化文件
│ └──auth.py # 认证相关的Blueprint

└───templates/ # 模板目录
└──auth/
login.html
register.html

创建Blueprint(blueprints/auth.py):

# 导入Blueprint类
# Blueprint用于创建可复用的路由模块
from flask import Blueprint, render_template, request, redirect, url_for

# 创建Blueprint实例
# 第一个参数'auth'是Blueprint的名称
# 第二个参数__name__用于确定Blueprint的根路径
# url_prefix='/auth'表示所有路由都会自动添加'/auth'前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

# 在Blueprint中定义路由
# 实际URL会是 /auth/login(因为有url_prefix)
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
password = request.form.get('password', '')

# 简单的验证(实际应用中应该连接数据库)
if username == 'admin' and password == '123456':
# 登录成功,重定向到首页
# url_for('index')会生成首页的URL
return redirect(url_for('index'))
else:
# 登录失败,显示错误信息
error = '用户名或密码错误'
return render_template('auth/login.html', error=error)
else:
# GET请求,显示登录表单
return render_template('auth/login.html')

# 定义注册路由
# 实际URL会是 /auth/register
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')

# 简单的验证
if not username or not email or not password:
error = '请填写所有字段'
return render_template('auth/register.html', error=error)

# 在实际应用中,这里应该将用户保存到数据库
# 这里只是简单返回成功信息
return f'<h1>注册成功!</h1><p>用户名:{username}</p><p><a href="/auth/login">去登录</a></p>'
else:
# GET请求,显示注册表单
return render_template('auth/register.html')

在主应用中注册Blueprint(app.py):

# 导入Flask类
from flask import Flask, render_template

# 导入Blueprint
# 从blueprints包中导入auth模块
from blueprints.auth import auth_bp

# 创建Flask应用实例
app = Flask(__name__)

# 注册Blueprint
# register_blueprint()方法将Blueprint注册到应用中
app.register_blueprint(auth_bp)

# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

创建模板文件(templates/auth/login.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
width: 100%;
}
.error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>用户登录</h1>

<!-- 如果有错误信息,显示错误 -->
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}

<!-- 登录表单 -->
<form method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>

<label for="password">密码:</label>
<input type="password" id="password" name="password" required>

<button type="submit">登录</button>
</form>

<p><a href="{{ url_for('auth.register') }}">还没有账号?去注册</a></p>
</body>
</html>

创建注册模板(templates/auth/register.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 400px;
margin: 50px auto;
padding: 20px;
}
form {
background-color: #f9f9f9;
padding: 20px;
border-radius: 5px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 3px;
box-sizing: border-box;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
width: 100%;
}
.error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<h1>用户注册</h1>

<!-- 如果有错误信息,显示错误 -->
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}

<!-- 注册表单 -->
<form method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>

<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>

<label for="password">密码:</label>
<input type="password" id="password" name="password" required>

<button type="submit">注册</button>
</form>

<p><a href="{{ url_for('auth.login') }}">已有账号?去登录</a></p>
</body>
</html>

6.4 多个Blueprint示例

让我们创建一个更完整的示例,包含多个Blueprint:

项目结构

my_app/
│ app.py

├───blueprints/
│ ├──__init__.py
│ ├──auth.py # 认证Blueprint
│ ├──blog.py # 博客Blueprint
│ └──api.py # API Blueprint

└───templates/
├──index.html
├──auth/
│ ├──login.html
│ └──register.html
└──blog/
├──list.html
└──detail.html

创建博客Blueprint(blueprints/blog.py):

# 导入Blueprint类和render_template函数
from flask import Blueprint, render_template

# 创建博客Blueprint
# url_prefix='/blog'表示所有路由都会添加'/blog'前缀
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

# 模拟博客文章数据
# 在实际应用中,这些数据来自数据库
posts = [
{
'id': 1,
'title': '第一篇文章',
'content': '这是我的第一篇博客文章内容。',
'author': '小明',
'date': '2024-01-01'
},
{
'id': 2,
'title': '学习Flask',
'content': '今天学习了Flask的Blueprint,非常有用!',
'author': '小红',
'date': '2024-01-02'
}
]

# 定义博客列表路由
# 实际URL是 /blog/
@blog_bp.route('/')
def list_posts():
# 渲染博客列表模板
return render_template('blog/list.html', posts=posts)

# 定义文章详情路由
# 实际URL是 /blog/<post_id>
@blog_bp.route('/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
post = next((p for p in posts if p['id'] == post_id), None)

# 如果文章不存在,返回404
if post is None:
return '<h1>文章未找到</h1>', 404

# 渲染文章详情模板
return render_template('blog/detail.html', post=post)

创建API Blueprint(blueprints/api.py):

# 导入Blueprint类和jsonify函数
from flask import Blueprint, jsonify

# 创建API Blueprint
# url_prefix='/api'表示所有路由都会添加'/api'前缀
api_bp = Blueprint('api', __name__, url_prefix='/api')

# 模拟用户数据
users = [
{'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'},
{'id': 2, 'name': '李四', 'email': 'lisi@example.com'},
]

# 定义获取所有用户的API
# 实际URL是 /api/users
@api_bp.route('/users', methods=['GET'])
def get_users():
# 返回JSON格式的用户列表
return jsonify(users)

# 定义获取单个用户的API
# 实际URL是 /api/users/<user_id>
@api_bp.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
# 根据ID查找用户
user = next((u for u in users if u['id'] == user_id), None)

# 如果用户不存在,返回404
if user is None:
return jsonify({'error': '用户未找到'}), 404

# 返回用户信息
return jsonify(user)

更新主应用(app.py):

# 导入Flask类
from flask import Flask, render_template

# 导入所有Blueprint
# 从blueprints包中导入各个Blueprint模块
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
from blueprints.api import api_bp

# 创建Flask应用实例
app = Flask(__name__)

# 注册所有Blueprint
# 将不同的Blueprint注册到应用中
app.register_blueprint(auth_bp) # 注册认证Blueprint
app.register_blueprint(blog_bp) # 注册博客Blueprint
app.register_blueprint(api_bp) # 注册API Blueprint

# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

创建首页模板(templates/index.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.nav {
background-color: #f0f0f0;
padding: 20px;
border-radius: 5px;
margin-bottom: 20px;
}
.nav a {
display: inline-block;
margin-right: 20px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 3px;
}
.nav a:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>欢迎访问</h1>

<div class="nav">
<h2>导航菜单</h2>
<!-- 使用url_for()生成Blueprint中的路由URL -->
<!-- 'auth.login'表示auth Blueprint中的login函数 -->
<a href="{{ url_for('auth.login') }}">登录</a>
<a href="{{ url_for('auth.register') }}">注册</a>
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>
<a href="{{ url_for('api.get_users') }}">API: 获取用户</a>
</div>

<p>这是一个使用Blueprint组织的Flask应用示例。</p>
<p>不同的功能被分离到不同的Blueprint中,代码更清晰、更易维护。</p>
</body>
</html>

6.5 Blueprint的URL前缀和模板

Blueprint可以有自己的URL前缀和模板目录。

带URL前缀的Blueprint

# 创建Blueprint时指定url_prefix
# 所有路由都会自动添加这个前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

# 这个路由的实际URL是 /auth/login
@auth_bp.route('/login')
def login():
return '登录页面'

Blueprint的模板目录

# 创建Blueprint时指定template_folder
# Blueprint会在这个目录中查找模板
blog_bp = Blueprint('blog', __name__,
url_prefix='/blog',
template_folder='blog_templates')

# 这个路由会从blog_templates目录中查找模板
@blog_bp.route('/')
def index():
return render_template('index.html') # 查找 blog_templates/index.html

完整示例

# blueprints/blog.py
from flask import Blueprint, render_template

# 创建Blueprint,指定URL前缀和模板目录
blog_bp = Blueprint('blog', __name__,
url_prefix='/blog',
template_folder='blog_templates')

# 定义路由
@blog_bp.route('/')
def index():
# 会从blog_templates目录查找模板
return render_template('index.html')

6.6 Blueprint之间的通信

不同的Blueprint可以通过url_for()函数相互引用。

示例

# blueprints/auth.py
from flask import Blueprint, render_template, redirect, url_for

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/login')
def login():
# 登录成功后,重定向到博客首页
# 'blog.index'表示blog Blueprint中的index函数
return redirect(url_for('blog.index'))

# blueprints/blog.py
from flask import Blueprint, render_template, url_for

blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

@blog_bp.route('/')
def index():
# 在模板中可以链接到其他Blueprint的路由
login_url = url_for('auth.login')
return render_template('blog/index.html', login_url=login_url)

6.7 Blueprint的最佳实践

1. 按功能模块组织

my_app/
│ app.py

├───blueprints/
│ ├──__init__.py
│ ├──auth.py # 认证相关
│ ├──blog.py # 博客相关
│ ├──admin.py # 管理后台
│ └──api.py # API接口

├───templates/
│ ├──auth/
│ ├──blog/
│ └──admin/

└───static/
├──css/
├──js/
└──images/

2. 每个Blueprint独立管理

# blueprints/blog.py
from flask import Blueprint, render_template

# 创建Blueprint
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

# 在这个Blueprint中定义所有博客相关的路由
@blog_bp.route('/')
def index():
return render_template('blog/index.html')

@blog_bp.route('/<int:post_id>')
def show_post(post_id):
return render_template('blog/detail.html', post_id=post_id)

# 可以定义Blueprint级别的错误处理
@blog_bp.errorhandler(404)
def not_found(error):
return render_template('blog/404.html'), 404

3. 在主应用中统一注册

# app.py
from flask import Flask

# 导入所有Blueprint
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp
from blueprints.api import api_bp

app = Flask(__name__)

# 统一注册所有Blueprint
app.register_blueprint(auth_bp)
app.register_blueprint(blog_bp)
app.register_blueprint(api_bp)

# 主应用只保留首页等核心路由
@app.route('/')
def index():
return '首页'

6.8 完整示例:使用Blueprint的博客系统

下面是一个完整的、使用Blueprint组织的博客系统:

项目结构

my_blog/
│ app.py

├───blueprints/
│ ├──__init__.py
│ ├──auth.py
│ └──blog.py

└───templates/
├──base.html
├──index.html
├──auth/
│ ├──login.html
│ └──register.html
└──blog/
├──list.html
└──detail.html

主应用文件(app.py):

# 导入Flask类
from flask import Flask, render_template

# 导入Blueprint
from blueprints.auth import auth_bp
from blueprints.blog import blog_bp

# 创建Flask应用实例
app = Flask(__name__)

# 注册Blueprint
app.register_blueprint(auth_bp)
app.register_blueprint(blog_bp)

# 定义首页路由
@app.route('/')
def index():
# 渲染首页模板
return render_template('index.html')

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

认证Blueprint(blueprints/auth.py):

# 导入Blueprint和相关函数
from flask import Blueprint, render_template, request, redirect, url_for

# 创建认证Blueprint
# url_prefix='/auth'表示所有路由都会添加'/auth'前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

# 定义登录路由
# 实际URL是 /auth/login
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
password = request.form.get('password', '')

# 简单验证
if username == 'admin' and password == '123456':
# 登录成功,重定向到博客列表
return redirect(url_for('blog.list_posts'))
else:
# 登录失败
error = '用户名或密码错误'
return render_template('auth/login.html', error=error)
else:
# GET请求,显示登录表单
return render_template('auth/login.html')

# 定义注册路由
# 实际URL是 /auth/register
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
# 判断请求方法
if request.method == 'POST':
# 获取表单数据
username = request.form.get('username', '')
email = request.form.get('email', '')
password = request.form.get('password', '')

# 简单验证
if not username or not email or not password:
error = '请填写所有字段'
return render_template('auth/register.html', error=error)

# 注册成功(实际应用中应该保存到数据库)
return f'<h1>注册成功!</h1><p>用户名:{username}</p><p><a href="/auth/login">去登录</a></p>'
else:
# GET请求,显示注册表单
return render_template('auth/register.html')

博客Blueprint(blueprints/blog.py):

# 导入Blueprint和render_template函数
from flask import Blueprint, render_template

# 创建博客Blueprint
# url_prefix='/blog'表示所有路由都会添加'/blog'前缀
blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

# 模拟博客文章数据
posts = [
{
'id': 1,
'title': '第一篇文章',
'content': '这是我的第一篇博客文章内容。',
'author': '小明',
'date': '2024-01-01'
},
{
'id': 2,
'title': '学习Flask Blueprint',
'content': 'Blueprint让Flask应用的组织更加清晰和模块化。',
'author': '小红',
'date': '2024-01-02'
}
]

# 定义博客列表路由
# 实际URL是 /blog/
@blog_bp.route('/')
def list_posts():
# 渲染博客列表模板
return render_template('blog/list.html', posts=posts)

# 定义文章详情路由
# 实际URL是 /blog/<post_id>
@blog_bp.route('/<int:post_id>')
def show_post(post_id):
# 根据ID查找文章
post = next((p for p in posts if p['id'] == post_id), None)

# 如果文章不存在,返回404
if post is None:
return '<h1>文章未找到</h1>', 404

# 渲染文章详情模板
return render_template('blog/detail.html', post=post)

创建必要的模板文件

创建 templates/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎访问博客系统</h1>
<nav>
<a href="{{ url_for('auth.login') }}">登录</a> |
<a href="{{ url_for('auth.register') }}">注册</a> |
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>
</nav>
</body>
</html>

创建 templates/blog/list.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>博客列表</title>
</head>
<body>
<h1>博客文章列表</h1>
<nav>
<a href="{{ url_for('index') }}">首页</a> |
<a href="{{ url_for('auth.login') }}">登录</a>
</nav>

{% for post in posts %}
<article>
<h3><a href="{{ url_for('blog.show_post', post_id=post.id) }}">{{ post.title }}</a></h3>
<p>作者:{{ post.author }} | 日期:{{ post.date }}</p>
<p>{{ post.content[:50] }}...</p>
</article>
<hr>
{% endfor %}
</body>
</html>

创建 templates/blog/detail.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>作者:{{ post.author }} | 日期:{{ post.date }}</p>
<div>{{ post.content }}</div>
<p><a href="{{ url_for('blog.list_posts') }}">← 返回列表</a></p>
</body>
</html>

6.9 Blueprint的常见用法总结

1. 基本创建

# 创建Blueprint
bp = Blueprint('name', __name__)

# 定义路由
@bp.route('/')
def index():
return 'Hello'

# 注册Blueprint
app.register_blueprint(bp)

2. 带URL前缀

# 所有路由都会添加'/prefix'前缀
bp = Blueprint('name', __name__, url_prefix='/prefix')

@bp.route('/hello') # 实际URL是 /prefix/hello
def hello():
return 'Hello'

3. 带模板目录

# Blueprint会在指定目录查找模板
bp = Blueprint('name', __name__, template_folder='my_templates')

@bp.route('/')
def index():
return render_template('index.html') # 从my_templates目录查找

4. 在模板中引用Blueprint路由

<!-- 使用url_for()生成Blueprint中的路由URL -->
<!-- 'blueprint_name.function_name'格式 -->
<a href="{{ url_for('blog.list_posts') }}">博客列表</a>

7. 静态文件

Web应用通常需要CSS、JavaScript、图片等静态文件。Flask可以轻松处理这些文件。

7.1 项目结构

my_app/
│ app.py

├───static/ # 静态文件目录
│ ├───css/
│ │ style.css
│ ├───js/
│ │ script.js
│ └───images/
│ logo.png

└───templates/ # 模板目录
index.html

7.2 使用静态文件

创建static/css/style.css

/* 全局样式 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}

/* 容器样式 */
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

/* 标题样式 */
h1 {
color: #4CAF50;
}

/* 按钮样式 */
.btn {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 3px;
cursor: pointer;
}

.btn:hover {
background-color: #45a049;
}

创建templates/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>我的应用</title>
<!-- 使用url_for()函数引用CSS文件 -->
<!-- 'static'是Flask内置的端点,filename是文件路径 -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<div class="container">
<h1>欢迎访问</h1>
<p>这是一个使用Flask和CSS的示例页面。</p>
<button class="btn">点击我</button>
</div>

<!-- 引用JavaScript文件 -->
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

创建static/js/script.js

// 等待页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
// 获取按钮元素
const btn = document.querySelector('.btn');

// 为按钮添加点击事件
btn.addEventListener('click', function() {
// 显示提示框
alert('按钮被点击了!');
});
});

更新app.py

# 导入Flask类和render_template函数
from flask import Flask, render_template

# 创建Flask应用实例
app = Flask(__name__)

# 定义首页路由
@app.route('/')
def index():
# 渲染模板(会自动加载CSS和JS)
return render_template('index.html')

# 运行应用
if __name__ == '__main__':
app.run(debug=True)

8. 常见问题和注意事项

8.1 调试模式

开发时建议开启调试模式,但生产环境必须关闭。

# 开发环境:开启调试模式
if __name__ == '__main__':
app.run(debug=True) # 开发时使用

# 生产环境:关闭调试模式
if __name__ == '__main__':
app.run(debug=False) # 生产环境必须关闭

8.2 端口被占用

如果5000端口被占用,可以修改端口:

# 修改端口为8080
if __name__ == '__main__':
app.run(port=8080, debug=True)

8.3 模板文件找不到

确保模板文件在正确的目录中:

# Flask默认在templates目录中查找模板
# 如果模板在其他目录,需要指定
app = Flask(__name__, template_folder='my_templates')

8.4 静态文件找不到

确保静态文件在正确的目录中:

# Flask默认在static目录中查找静态文件
# 如果静态文件在其他目录,需要指定
app = Flask(__name__, static_folder='my_static')