Python Flask 教程:从入门到掌握
欢迎来到 Flask 的世界!Flask 是一个轻量级的 Python Web 框架,它不依赖外部库(除了 Werkzeug 和 Jinja2),这使得它非常灵活,你可以根据自己的需求选择性地添加组件。与全功能的框架(如 Django)不同,Flask 提供了构建 Web 应用的核心功能,而将数据库、表单验证、用户认证等高级功能留给开发者通过扩展来实现。这使得 Flask 成为开发小型到中型 Web 应用、API 接口或微服务的理想选择。
本教程将带你从零开始,一步步深入 Flask 的世界,最终掌握构建实际 Web 应用所需的技能。
目标读者:
- 具备基本的 Python 编程知识。
- 对 Web 开发感兴趣,想学习如何用 Python 构建网站或 API。
- 希望学习一个灵活、易于上手的 Web 框架。
我们将涵盖的主题:
- 入门基础: 安装 Flask,创建第一个“Hello, World”应用。
- 路由与视图函数: 如何定义 URL,如何处理请求。
- 模板引擎: 使用 Jinja2 渲染动态 HTML 页面。
- 静态文件: 处理 CSS, JavaScript 和图片等静态资源。
- 请求与响应: 获取表单数据、URL 参数,发送不同类型的响应。
- 数据库集成: 使用 SQLAlchemy 操作数据库(以 SQLite 为例)。
- 表单处理: 使用 Flask-WTF 构建和验证 Web 表单。
- 用户认证基础: 简单的用户登录/注销概念介绍。
- 应用结构: 使用蓝图(Blueprints)组织大型应用。
- 错误处理: 创建自定义错误页面。
- 配置管理: 管理不同环境下的应用配置。
- 部署概念: 将 Flask 应用部署到生产环境。
- 进阶与生态: 探索 Flask 的扩展和更高级主题。
让我们开始吧!
第一部分:入门基础
1. 安装 Flask
首先,你需要安装 Python。推荐使用 Python 3.6 或更高版本。
在安装 Flask 之前,强烈建议使用虚拟环境(Virtual Environment)。虚拟环境可以隔离不同项目所需的 Python 包,避免版本冲突。
-
创建虚拟环境:
bash
python -m venv venv
这会在当前目录下创建一个名为venv
的文件夹,其中包含了虚拟环境。 -
激活虚拟环境:
- 在 macOS/Linux 上:
bash
source venv/bin/activate - 在 Windows 上:
bash
venv\Scripts\activate
激活后,你的终端提示符前会显示(venv)
字样。
- 在 macOS/Linux 上:
-
安装 Flask:
确保虚拟环境已激活,然后使用 pip 安装 Flask:
bash
pip install Flask
2. 第一个“Hello, World”应用
创建一个名为 app.py
的文件,输入以下代码:
“`python
app.py
from flask import Flask
创建 Flask 应用实例
name 是一个特殊的 Python 变量,用于指示模块名。
Flask 用它来确定应用根目录,以便找到资源文件(如模板和静态文件)。
app = Flask(name)
定义一个路由 (Route) 和对应的视图函数 (View Function)
@app.route(‘/’) 装饰器将 URL ‘/’ 绑定到 index 函数
@app.route(‘/’)
def index():
“””
这是处理根 URL ‘/’ 的视图函数。
它返回一个简单的字符串作为响应。
“””
return ‘Hello, World!’
如果直接运行这个脚本,启动开发服务器
if name == ‘main‘:
# debug=True 可以在开发期间提供有用的错误信息和自动重载功能
app.run(debug=True)
“`
from flask import Flask
: 从flask
库导入Flask
类。app = Flask(__name__)
: 创建一个Flask
应用实例。__name__
是 Python 的内置变量,表示当前模块的名称。Flask 需要知道它在哪里,以便查找模板和静态文件。@app.route('/')
: 这是一个装饰器,将 URL 路径/
映射到紧随其后的index
函数。当用户访问应用的根 URL 时,就会执行index
函数。def index():
: 这是一个视图函数。它负责处理来自客户端的请求,并返回一个响应。在这里,它返回一个简单的字符串'Hello, World!'
。Flask 会自动将这个字符串封装成一个 HTTP 响应。if __name__ == '__main__': app.run(debug=True)
: 这段代码确保只有当你直接运行app.py
文件时,才会启动 Flask 的开发服务器。debug=True
开启调试模式,这在开发过程中非常有用,它会提供详细的错误报告,并在代码修改后自动重新加载应用。
3. 运行应用
在终端中,确保虚拟环境已激活,然后运行:
bash
python app.py
你会看到类似以下的输出:
* Serving Flask app 'app' (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: XXX-XXX-XXX
在浏览器中打开 http://127.0.0.1:5000/
,你将看到 “Hello, World!”。恭喜,你已经成功创建并运行了第一个 Flask 应用!
第二部分:路由与视图函数
路由是将 URL 映射到 Python 函数的过程。视图函数是处理请求并返回响应的函数。
1. 定义更多路由
你可以在 app
实例上使用 @app.route()
装饰器定义多个路由:
“`python
app.py (续)
@app.route(‘/about’)
def about():
“””
处理 ‘/about’ URL 的视图函数。
“””
return ‘This is the About page.’
@app.route(‘/contact’)
def contact():
“””
处理 ‘/contact’ URL 的视图函数。
“””
return ‘Get in touch with us!’
“`
现在访问 http://127.0.0.1:5000/about
和 http://127.0.0.1:5000/contact
,将看到对应的页面内容。
2. 动态路由
路由可以包含变量部分,允许你在 URL 中捕获参数。
“`python
app.py (续)
<> 中的部分是变量, 表示捕获一个字符串类型的变量,命名为 name
@app.route(‘/user/
def show_user_profile(name):
“””
处理 ‘/user/
name 参数从 URL 中捕获并作为参数传递给函数。
“””
return f’User: {name}’
可以指定不同的变量类型,如
@app.route(‘/post/
def show_post(post_id):
“””
处理 ‘/post/
post_id 参数从 URL 中捕获并作为整数传递给函数。
“””
return f’Post ID: {post_id}’
默认变量类型是 string
@app.route(‘/hello/
def hello_name(name):
return f’Hello, {name}!’
“`
访问 http://1.27.0.0.1:5000/user/Alice
将显示 “User: Alice”,访问 http://127.0.0.1:5000/post/123
将显示 “Post ID: 123″。
3. URL 构建 (url_for
)
在模板或重定向时,硬编码 URL 是不安全的,因为如果路由规则发生变化(例如 URL 路径或参数名改变),所有硬编码的 URL 都需要手动更新。Flask 提供了 url_for()
函数来动态生成 URL。它接收视图函数名作为第一个参数,以及任何 URL 变量作为关键字参数。
“`python
app.py (续)
from flask import url_for, redirect
@app.route(‘/’)
def index():
return ‘Hello, World! Go to About page.’
@app.route(‘/about’)
def about():
return ‘This is the About page. Go back to Home.’
@app.route(‘/profile/
def profile(username):
return f'{username}\’s profile’
@app.route(‘/login’)
def login():
# 模拟登录后重定向到用户主页
# url_for(‘profile’, username=’testuser’) 会生成 ‘/profile/testuser’
return redirect(url_for(‘profile’, username=’testuser’))
… (其他路由和 app.run 代码)
“`
使用 url_for()
的好处:
* 当你的 URL 规则改变时,你只需要更新 @app.route()
装饰器,url_for()
会自动生成正确的 URL。
* 它能正确处理 URL 参数,并进行适当的编码。
第三部分:模板引擎
视图函数返回的通常不是简单的字符串,而是 HTML 页面。直接在 Python 代码中拼接 HTML 字符串非常繁琐且容易出错。Flask 使用 Jinja2 作为默认的模板引擎,它允许你使用类似 HTML 的语法编写模板文件,并在其中嵌入 Python 代码逻辑(如变量、循环、条件判断)。
1. 配置模板
Flask 默认会在应用根目录下的一个名为 templates
的文件夹中查找模板文件。你需要创建这个文件夹。
your_flask_app/
├── app.py
└── templates/
└── index.html
└── about.html
2. 编写模板文件 (templates/index.html
)
“`html
Hello from Template!
This content is rendered using Jinja2.
“`
3. 在视图函数中渲染模板
使用 render_template
函数来渲染模板。
“`python
app.py (续)
from flask import render_template # 导入 render_template 函数
@app.route(‘/’)
def index():
“””
渲染 index.html 模板。
“””
return render_template(‘index.html’)
@app.route(‘/hello/
def hello_name(name):
“””
渲染一个模板,并将变量传递给模板。
“””
return render_template(‘hello.html’, user_name=name) # 将 name 变量传递给模板,在模板中可以通过 user_name 访问
“`
创建 templates/hello.html
:
“`html
Hello, {{ user_name }}!
{# 变量 user_name 的值会在这里渲染 #}
Welcome to my Flask application.
“`
访问 http://127.0.0.1:5000/
将看到 index.html
的内容,访问 http://127.0.0.1:5000/hello/Alice
将看到 hello.html
的内容,其中 {{ user_name }}
被替换为 “Alice”。
4. Jinja2 语法基础
- 变量:
{{ variable_name }}
。可以在 Python 视图函数中将变量作为关键字参数传递给render_template
。 - 控制结构:
{% ... %}
用于编写控制语句,如if/else
和for
循环。- 条件判断:
html
{% if user %}
<p>Welcome, {{ user.name }}!</p>
{% else %}
<p>Please log in.</p>
{% endif %} - 循环:
html
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
- 条件判断:
- 注释:
{# 这是注释 #}
,不会被渲染到最终的 HTML 中。 -
模板继承: Jinja2 支持模板继承,可以定义一个基础模板,其他模板可以继承它并覆盖特定的块。这非常有利于网站整体布局的一致性。
-
创建基础模板
templates/base.html
:
“`html
<!DOCTYPE html>
{% block title %}My App{% endblock %} {# 定义一个 block,子模板可以覆盖 #}
{# 引入静态文件将在下一节介绍 #}
My Awesome App
<main> {% block content %}{% endblock %} {# 定义主要内容的 block #} </main> <footer> <p>© 2023 My App</p> </footer>
“` -
继承基础模板
templates/index.html
:
“`html
{% extends “base.html” %} {# 继承 base.html 模板 #}{% block title %}Home – My App{% endblock %} {# 覆盖 title block #}
{% block content %} {# 覆盖 content block #}
Welcome to the Home Page
This is the main content area.
{% endblock %}
* 继承基础模板 `templates/about.html`:
html
{% extends “base.html” %}{% block title %}About – My App{% endblock %}
{% block content %}
About Us
Learn more about our project.
{% endblock %}
* 在视图函数中渲染时,只需渲染子模板即可:
python
@app.route(‘/’)
def index():
return render_template(‘index.html’)@app.route(‘/about’)
def about():
return render_template(‘about.html’)
“`
-
第四部分:静态文件
静态文件是指不会动态生成,直接提供给客户端的文件,如 CSS 样式表、JavaScript 脚本、图片、字体等。
Flask 默认会在应用根目录下的一个名为 static
的文件夹中查找静态文件。
your_flask_app/
├── app.py
├── templates/
│ └── ...
└── static/
├── css/
│ └── style.css
└── images/
└── logo.png
1. 引用静态文件
在模板文件中,使用 url_for()
函数并指定端点名为 'static'
,然后通过 filename
参数指定静态文件相对于 static
目录的路径来引用静态文件。
“`html
{# templates/base.html (续) #}
{# 引入 CSS 文件 #}
 }})
My Awesome App
{% block content %}{% endblock %}
{# 引入 JavaScript 文件 (如果需要的话) #}
{# #}
“`
创建 static/css/style.css
:
“`css
/ static/css/style.css /
body {
font-family: sans-serif;
margin: 20px;
}
header {
background-color: #f0f0f0;
padding: 10px;
text-align: center;
}
main {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
}
footer {
margin-top: 20px;
text-align: center;
font-size: 0.8em;
color: #666;
}
“`
将一个图片文件(例如 logo.png
)放入 static/images
目录。重新运行应用,你将在页面中看到应用了样式的元素和引入的图片。
第五部分:请求与响应
Web 应用的核心是处理来自客户端的请求(Request)并返回响应(Response)。Flask 提供了 request
对象来访问客户端发送的数据。
1. 获取请求数据
导入 request
对象:from flask import request
。
-
URL 查询参数 (Query Parameters): 在 URL 问号
?
后的参数,如/search?q=flask&page=1
。
“`python
# app.py (续)
from flask import request@app.route(‘/search’)
def search():
query = request.args.get(‘q’) # 获取 q 参数的值,如果不存在则返回 None
page = request.args.get(‘page’, type=int, default=1) # 获取 page 参数,尝试转为整数,失败或不存在则用默认值 1
if query:
return f’Searching for: “{query}”, Page: {page}’
else:
return ‘Please provide a search query (e.g., /search?q=python)’
``
request.args` 是一个类字典对象,用于访问 GET 请求的查询参数。 -
表单数据 (Form Data): 通过 POST 请求提交的表单数据。
python
# app.py (续)
@app.route('/submit_form', methods=['GET', 'POST']) # 允许 GET 和 POST 方法
def submit_form():
if request.method == 'POST':
# 获取表单字段的值
username = request.form.get('username')
password = request.form.get('password')
# 处理数据 (例如保存到数据库)
return f'Form submitted. Username: {username}, Password: {password}'
# 如果是 GET 请求,通常是显示表单页面
return '''
<form method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Submit">
</form>
'''
request.form
是一个类字典对象,用于访问 POST 请求的表单数据。 -
JSON 数据: 用于处理 API 请求中发送的 JSON 数据。
python
# app.py (续)
@app.route('/api/data', methods=['POST'])
def api_data():
if request.is_json:
data = request.json # 获取解析后的 JSON 数据
# 处理数据
return {"received_data": data, "status": "success"}, 200 # 返回 JSON 响应和状态码
else:
return {"error": "Request must be JSON"}, 415 # 返回错误响应和状态码
request.json
会尝试解析请求体中的 JSON 数据。request.is_json
可以检查请求的Content-Type
是否是 JSON。 -
请求头 (Headers): 访问 HTTP 请求头信息。
python
# app.py (续)
@app.route('/headers')
def show_headers():
user_agent = request.headers.get('User-Agent') # 获取 User-Agent 头
return f'Your User Agent is: {user_agent}'
request.headers
是一个类字典对象,包含所有请求头。
2. 返回不同类型的响应
除了字符串和渲染的模板,视图函数还可以返回:
- HTML: 通过字符串或模板渲染。
-
JSON: 使用
jsonify
函数 (从flask
导入)。
“`python
from flask import jsonify@app.route(‘/users’)
def get_users():
# 假设从数据库获取用户列表
users = [
{‘id’: 1, ‘name’: ‘Alice’},
{‘id’: 2, ‘name’: ‘Bob’}
]
return jsonify(users) # 返回 JSON 格式的响应
* **重定向 (Redirect):** 使用 `redirect` 函数。
python
from flask import redirect, url_for@app.route(‘/old-page’)
def old_page():
return redirect(url_for(‘index’)) # 重定向到 index 视图函数对应的 URL
``
return “Not Found”, 404`。
* **特定状态码:** 可以将响应内容和状态码一起返回,例如
第六部分:数据库集成
Web 应用通常需要存储和检索数据。虽然 Flask 本身不提供数据库功能,但可以轻松集成各种数据库和 ORM(对象关系映射)工具。SQLAlchemy 是 Python 生态中最流行的 ORM 之一,而 Flask-SQLAlchemy 是一个 Flask 扩展,简化了 SQLAlchemy 在 Flask 中的使用。
1. 安装 Flask-SQLAlchemy
bash
pip install Flask-SQLAlchemy
2. 配置数据库
在应用中配置数据库连接 URI 和创建 SQLAlchemy 实例。我们将使用简单易用的 SQLite 数据库。
“`python
app.py (续)
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy # 导入 SQLAlchemy
app = Flask(name)
数据库配置
SQLite 数据库文件位于应用根目录
app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///site.db’
禁止跟踪对象的修改,可以节省内存开销
app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False
db = SQLAlchemy(app) # 创建 SQLAlchemy 实例
定义一个简单的模型(数据库表)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True) # 主键,自动增长
title = db.Column(db.String(100), nullable=False) # 字符串类型,非空
content = db.Column(db.Text, nullable=False) # 文本类型,非空
def __repr__(self):
# 用于打印对象时显示信息
return f"Post('{self.title}', '{self.content[:20]}...')"
在应用上下文内创建数据库表
通常在第一次运行时执行一次
with app.app_context():
db.create_all()
# 可以在这里添加一些初始数据,但通常是在单独的脚本中做
… (其他路由和 app.run)
“`
app.config['SQLALCHEMY_DATABASE_URI']
: 配置数据库的连接 URI。sqlite:///site.db
表示在应用根目录下创建一个名为site.db
的 SQLite 数据库文件。db = SQLAlchemy(app)
: 创建SQLAlchemy
实例,并将其与 Flask 应用关联。class Post(db.Model):
: 定义一个名为Post
的类,它继承自db.Model
。这个类代表数据库中的一张表。类属性(如id
,title
,content
)代表表中的列。db.Column(...)
: 定义列的类型和约束。with app.app_context(): db.create_all()
: 在 Flask 应用的上下文环境中执行db.create_all()
,这会根据你定义的模型创建相应的数据库表。通常这个操作只需要执行一次(或者使用迁移工具)。
3. 数据库操作 (CRUD)
-
创建 (Create):
python
# 创建一个新 Post 对象
new_post = Post(title='First Post', content='This is the content of the first post.')
# 将对象添加到会话
db.session.add(new_post)
# 提交会话,将对象保存到数据库
db.session.commit() -
读取 (Read):
“`python
# 查询所有 Post
all_posts = Post.query.all()根据主键查询单个 Post
post_by_id = Post.query.get(1) # 查询 id 为 1 的 Post
根据条件过滤查询
查询 title 为 ‘First Post’ 的所有 Post
posts_with_title = Post.query.filter_by(title=’First Post’).all()
查询 title 包含 ‘Post’ 的所有 Post (使用 like)
posts_like_title = Post.query.filter(Post.title.like(‘%Post%’)).all()
“` -
更新 (Update):
python
post_to_update = Post.query.get(1)
if post_to_update:
post_to_update.title = 'Updated Title'
post_to_update.content = 'New and improved content!'
db.session.commit() # 提交会话以保存更改 -
删除 (Delete):
python
post_to_delete = Post.query.get(2)
if post_to_delete:
db.session.delete(post_to_delete) # 从会话中删除对象
db.session.commit() # 提交会话以从数据库中删除
4. 在视图函数中使用数据库
“`python
app.py (续)
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
… (app, config, db, Post Model definition) …
@app.route(‘/’)
def index():
# 从数据库获取所有 Post
posts = Post.query.all()
# 渲染模板并传递 posts 数据
return render_template(‘index.html’, posts=posts)
@app.route(‘/add_post’, methods=[‘GET’, ‘POST’])
def add_post():
if request.method == ‘POST’:
title = request.form[‘title’]
content = request.form[‘content’]
new_post = Post(title=title, content=content)
db.session.add(new_post)
db.session.commit()
return redirect(url_for(‘index’)) # 添加成功后重定向回首页
# GET 请求显示添加文章的表单
return render_template(‘add_post.html’) # 你需要创建 add_post.html 模板
@app.route(‘/post/
def view_post(post_id):
# 根据 id 查询文章,如果找不到则返回 404 错误
post = Post.query.get_or_404(post_id)
return render_template(‘view_post.html’, post=post) # 你需要创建 view_post.html 模板
… (if name == ‘main‘: …)
“`
创建相应的模板文件(index.html
、add_post.html
、view_post.html
),在其中使用 Jinja2 语法遍历和显示数据,以及创建表单。
第七部分:表单处理 (Flask-WTF)
处理 Web 表单涉及显示表单、验证用户输入、处理错误以及接收数据。 Flask-WTF 是一个 Flask 扩展,它集成了 WTForms 库,极大地简化了这些任务,特别是提供了 CSRF(跨站请求伪造)保护。
1. 安装 Flask-WTF
bash
pip install Flask-WTF
2. 配置 Secret Key
Flask-WTF 需要一个 Secret Key 来生成 CSRF token 和加密 session。
“`python
app.py (续)
app = Flask(name)
… SQLAlchemy config …
必须配置一个 Secret Key
在生产环境中,这个key应该是一个复杂、随机且保密的值,并且不应该直接写在代码中
app.config[‘SECRET_KEY’] = ‘your_very_secret_random_key_here’
db = SQLAlchemy(app)
… Post Model …
“`
重要: 在生产环境中,不要将 Secret Key 直接写在代码中,而是通过环境变量或其他安全方式配置。
3. 定义表单类
创建一个 Python 文件(例如 forms.py
)来定义表单类:
“`python
forms.py
from flask_wtf import FlaskForm # 导入 FlaskForm
from wtforms import StringField, TextAreaField, SubmitField # 导入常用字段类型
from wtforms.validators import DataRequired, Length # 导入常用的验证器
class PostForm(FlaskForm):
# 定义一个标题字段,类型为字符串输入框
# DataRequired() 验证器确保字段不为空
# Length() 验证器限制字段长度
title = StringField(‘Title’, validators=[DataRequired(), Length(max=100)])
# 定义一个内容字段,类型为文本区域
content = TextAreaField('Content', validators=[DataRequired()])
# 定义一个提交按钮
submit = SubmitField('Submit')
“`
4. 在视图函数中使用表单
导入表单类,创建表单实例,并在视图函数中处理它。
“`python
app.py (续)
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from forms import PostForm # 从 forms.py 导入 PostForm
… (app, config, db, Post Model) …
@app.route(‘/add_post’, methods=[‘GET’, ‘POST’])
def add_post():
form = PostForm() # 创建表单实例
# form.validate_on_submit() 会在 POST 请求时验证表单数据,并在验证通过时返回 True
if form.validate_on_submit():
# 从表单对象中获取数据
title = form.title.data
content = form.content.data
new_post = Post(title=title, content=content)
db.session.add(new_post)
db.session.commit()
# Flash 消息(可选,需要 Flask-Flash 扩展或手动实现)
# flash('Your post has been created!', 'success')
return redirect(url_for('index')) # 添加成功后重定向回首页
# 如果是 GET 请求或表单验证失败,渲染模板并传递表单对象
return render_template('add_post.html', form=form)
“`
5. 在模板中渲染表单
使用 Jinja2 语法渲染表单字段。
“`html
{# templates/add_post.html (假设继承了 base.html) #}
{% extends “base.html” %}
{% block title %}Add New Post – My App{% endblock %}
{% block content %}
Add New Post
{# action 属性可以省略,默认提交到当前 URL #}
{# method 必须是 POST #}