文章目录
-
- [前言:为什么要学习 Flask?](#前言:为什么要学习 Flask?)
- [一、Flask 的基石 - 核心对象与生命周期](#一、Flask 的基石 - 核心对象与生命周期)
-
- [1.1 Flask 应用实例](#1.1 Flask 应用实例)
- [1.2 应用配置](#1.2 应用配置)
- [二、路由系统 - 请求的入口](#二、路由系统 - 请求的入口)
-
- [2.1 基本路由定义](#2.1 基本路由定义)
- [2.2 动态路由与变量规则](#2.2 动态路由与变量规则)
- [2.3 HTTP 方法](#2.3 HTTP 方法)
- [2.4 URL 构建 (`url_for`)](#2.4 URL 构建 (
url_for))
- [三、模板引擎 - Jinja2 的艺术](#三、模板引擎 - Jinja2 的艺术)
-
- [3.1 渲染模板](#3.1 渲染模板)
- [3.2 Jinja2 语法详解](#3.2 Jinja2 语法详解)
- [四、请求与响应 - 与客户端的对话](#四、请求与响应 - 与客户端的对话)
-
- [4.1 全局请求对象 (`request`)](#4.1 全局请求对象 (
request)) - [4.2 构建响应](#4.2 构建响应)
- [4.3 重定向](#4.3 重定向)
- [4.1 全局请求对象 (`request`)](#4.1 全局请求对象 (
- [五、会话管理 - 记住用户的状态](#五、会话管理 - 记住用户的状态)
-
- [5.1 使用 Session](#5.1 使用 Session)
- [六、高级功能 - 蓝图与钩子](#六、高级功能 - 蓝图与钩子)
-
- [6.1 蓝图 - 模块化应用](#6.1 蓝图 - 模块化应用)
- [6.2 请求钩子](#6.2 请求钩子)
- [七、Flask 扩展生态系统](#七、Flask 扩展生态系统)
-
- [7.1 Flask-SQLAlchemy - 数据库 ORM](#7.1 Flask-SQLAlchemy - 数据库 ORM)
- [7.2 Flask-Migrate - 数据库迁移](#7.2 Flask-Migrate - 数据库迁移)
- [7.3 Flask-WTF - 表单处理与 CSRF 防护](#7.3 Flask-WTF - 表单处理与 CSRF 防护)
- 八、部署
-
- [8.1 WSGI的方式](#8.1 WSGI的方式)
- [8.2 Gunicorn的方式](#8.2 Gunicorn的方式)
- [8.3 Nginx的方式](#8.3 Nginx的方式)
前言:为什么要学习 Flask?
在 Python Web 开发的世界里,Django 和 Flask 是两座巍峨的高峰。Django 如同一座装备齐全的"全能战舰",自带 ORM、后台管理、表单系统等,适合快速构建功能复杂的大型项目。而 Flask 则像一艘性能卓越的"快艇",它只提供最核心的工具:路由和请求处理。这种"微"哲学带来了无与伦比的灵活性和自由度,我们可以按需选择最适合的数据库、表单库或其他组件,打造完全定制化的应用。
Flask 虽然被称为"微框架",但其内涵博大精深,其"微"体现在核心简洁,而强大的扩展性则赋予了它无限可能。
一、Flask 的基石 - 核心对象与生命周期
在编写任何 Flask 应用之前,理解其核心对象 Flask 以及应用的生命周期至关重要。
1.1 Flask 应用实例
这行代码是每个 Flask 应用的起点,它创建了一个 Flask 类的实例。
python
from flask import Flask
app = Flask(__name__)
__name__参数的作用是什么?- 这是一个 Python 的特殊变量。当你的模块被直接运行时(如
python app.py),它的值是'__main__'。当它被其他模块导入时,它的值是模块的名字(如'app')。 - Flask 利用这个值来确定应用的根目录,从而能够正确地找到
templates和static文件夹的位置。例如,render_template('index.html')会在应用根目录/templates/下寻找文件。所以,通常情况下,总是传入__name__。
- 这是一个 Python 的特殊变量。当你的模块被直接运行时(如
1.2 应用配置
Flask 的配置是一个类似字典的对象 app.config,用于存储应用运行时所需的各种配置变量。
python
app = Flask(__name__)
# 设置配置
app.config['DEBUG'] = True # 开启调试模式
app.config['SECRET_KEY'] = 'a-very-secret-key' # 用于会话加密等
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db' # 数据库URI
# 从配置文件加载
# app.config.from_object('config.ProductionConfig')
常用配置项:
DEBUG:True/False。开启后,代码修改会自动重启服务,且发生错误时会在浏览器显示详细错误堆栈,开发时务必开启。SECRET_KEY: 一个长随机字符串。它对于 Flask 的会话管理、以及一些安全相关的扩展(如 Flask-WTF)至关重要,生产环境必须设置一个强密钥。TESTING:True/False。开启测试模式,会改变一些行为,如禁用错误捕获。PERMANENT_SESSION_LIFETIME: 设置session的持久化时间,是一个datetime.timedelta对象。
二、路由系统 - 请求的入口
路由是 Web 框架的核心,它负责将用户访问的 URL 映射到相应的 Python 处理函数。
2.1 基本路由定义
使用 @app.route() 装饰器来定义路由。
python
@app.route('/')
def index():
return "这是首页"
@app.route('/hello')
def hello():
return "Hello, Flask!"
2.2 动态路由与变量规则
这是 Flask 路由强大之处,可以在 URL 中捕获变量。
python
# 默认类型是 string,接受任何不包含斜杠的文本
@app.route('/user/<username>')
def show_user_profile(username):
return f'用户: {username}'
# 指定类型为 int,只接受整数
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'文章 ID: {post_id}'
# 指定类型为 float,只接受浮点数
@app.route('/price/<float:price>')
def show_price(price):
return f'价格: {price}'
# 指定类型为 path,接受包含斜杠的文本
@app.route('/files/<path:subpath>')
def show_subpath(subpath):
return f'子路径: {subpath}'
支持的转换器类型:
string: (默认) 字符串int: 整数float: 浮点数path: 路径,可以包含/
2.3 HTTP 方法
Web 不仅仅是 GET 请求。@app.route() 装饰器可以接受一个 methods 参数来指定它响应的 HTTP 方法列表。
python
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 处理登录逻辑
return "处理登录请求"
else:
# 显示登录表单
return "显示登录表单"
GET: 获取资源POST: 创建新资源PUT: 更新整个资源PATCH: 部分更新资源DELETE: 删除资源
2.4 URL 构建 (url_for)
在代码中硬编码 URL 是一个坏习惯。url_for() 函数可以根据视图函数名生成对应的 URL,这样可以避免因 URL 规则修改而导致大量代码改动。
python
from flask import url_for
@app.route('/')
def index():
# 生成 login 视图的 URL
login_url = url_for('login')
return f'<a href="{login_url}">去登录</a>'
@app.route('/login')
def login():
return "登录页"
url_for 还可以处理动态路由:
python
# url_for('show_user_profile', username='john_doe')
# 将生成 '/user/john_doe'
三、模板引擎 - Jinja2 的艺术
将业务逻辑和表现层分离是现代 Web 开发的最佳实践。Flask 默认集成了强大的 Jinja2 模板引擎。
3.1 渲染模板
使用 render_template() 函数来渲染模板。Flask 会在 templates 文件夹中寻找模板文件。
python
from flask import render_template
@app.route('/profile/<name>')
def profile(name):
# 将变量传递给模板
return render_template('profile.html', name=name, age=30)
templates/profile.html:
html
<!DOCTYPE html>
<html>
<head>
<title>个人资料</title>
</head>
<body>
<h1>你好, {{ name }}!</h1>
<p>你的年龄是: {{ age }}</p>
</body>
</html>
3.2 Jinja2 语法详解
1、变量
使用 {``{ ... }} 来输出变量。
html
<p>{{ my_variable }}</p>
Jinja2 可以访问 Python 对象的属性和方法:
html
<p>{{ user.name }}</p>
<p>{{ user.get_name() }}</p>
2、控制结构 - 条件判断
使用 {% ... %} 来包裹控制语句。
html
{% if user.is_authenticated %}
<h1>欢迎回来, {{ user.username }}!</h1>
{% else %}
<h1>请先登录</h1>
{% endif %}
3、控制结构 - 循环
html
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% else %}
<li>没有找到任何文章。</li>
{% endfor %}
在循环内部,你可以访问一些特殊变量:
loop.index: 当前迭代的索引(从1开始)loop.index0: 当前迭代的索引(从0开始)loop.first: 是否是第一次迭代loop.last: 是否是最后一次迭代
4、模板继承
这是 Jinja2 最强大的功能之一,可以创建一个包含所有公共元素(如页头、页脚、导航栏)的"基础模板",然后让子模板继承并覆盖特定部分。
templates/base.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}我的网站{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<header>
<h1>我的网站</h1>
<nav>...</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2023 我的网站</p>
</footer>
</body>
</html>
templates/child.html:
html
{% extends "base.html" %}
{% block title %}
首页 - {{ super() }}
{% endblock %}
{% block content %}
<h2>欢迎来到首页!</h2>
<p>这里是页面的主要内容。</p>
{% endblock %}
{% extends "base.html" %}: 声明继承自哪个模板。{% block block_name %}...{% endblock %}: 定义一个可以被子模板覆盖的"块"。{``{ super() }}: 在子模板块中调用父模板同名块的内容。
5、宏
宏类似于编程语言中的函数,用于定义可重用的 HTML 片段。
templates/macros.html:
html
{% macro render_field(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<div class="invalid-feedback">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
在其他模板中使用:
html
{% from 'macros.html' import render_field %}
<form method="post">
{{ render_field(form.username) }}
{{ render_field(form.password) }}
<input type="submit" value="Submit">
</form>
四、请求与响应 - 与客户端的对话
Web 应用的本质就是处理请求并返回响应。
4.1 全局请求对象 (request)
request 对象包含了客户端发出的 HTTP 请求的全部信息。注意:request 是一个代理对象,只在请求上下文中有效。
python
from flask import request
@app.route('/search')
def search():
# 获取查询字符串参数 ?q=flask
query = request.args.get('q', '') # .get(key, default) 方法更安全
return f'你搜索的关键词是: {query}'
@app.route('/login', methods=['POST'])
def do_login():
# 获取表单数据
username = request.form['username']
password = request.form['password']
# 获取 JSON 数据
# data = request.get_json()
return f'用户 {username} 尝试登录'
request 常用属性:
request.method: HTTP 方法 ('GET', 'POST' 等)request.form: 包含 POST 或 PUT 请求中的表单数据的字典。request.args: 包含 URL 查询字符串参数的字典。request.values:form和args的合集。request.files: 包含上传文件的字典。request.cookies: 包含客户端 cookies 的字典。request.headers: 包含 HTTP 头的字典。request.get_json(): 解析请求体中的 JSON 数据,返回 Python 字典。
4.2 构建响应
视图函数的返回值会自动被 Flask 转换为一个响应对象。
-
返回字符串 : Flask 会将其包装成一个以字符串为体、状态码为 200 OK、MIME 类型为
text/html的响应。 -
返回元组 :
(response, status, headers)或(response, status)。python@app.route('/not-found') def not_found(): return '页面未找到', 404 @app.route('/api/data') def api_data(): return {'key': 'value'}, 200, {'X-Custom-Header': 'SomeValue'} -
使用
make_response: 创建一个Response对象,可以进行更精细的控制。pythonfrom flask import make_response @app.route('/cookie') def set_cookie(): resp = make_response('设置 Cookie 成功') resp.set_cookie('username', 'john') return resp -
返回 JSON : 使用
jsonify函数,它会设置正确的Content-Type头 (application/json)。pythonfrom flask import jsonify @app.route('/api/user') def get_user(): return jsonify(username='john', email='john@example.com')
4.3 重定向
使用 redirect 函数可以将用户重定向到另一个 URL。
python
from flask import redirect, url_for
@app.route('/old-page')
def old_page():
# 重定向到 new_page 视图
return redirect(url_for('new_page'))
@app.route('/new-page')
def new_page():
return "这是新页面"
五、会话管理 - 记住用户的状态
HTTP 是无状态的,session(会话)技术是解决这个问题的关键,它允许我们在不同请求之间存储用户特定信息。
Flask 的 Session 是基于 客户端签名 Cookie 实现的。这意味着所有会话数据都经过加密签名后存储在用户的浏览器中。优点是无需服务端存储,缺点是不能存储敏感数据,且有大小限制。
5.1 使用 Session
python
from flask import session
# 必须设置 SECRET_KEY
app.config['SECRET_KEY'] = 'your-secret-key-here'
@app.route('/set_session')
def set_session():
session['username'] = 'testuser'
session['user_id'] = 123
return 'Session 已设置'
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
user_id = session.get('user_id')
return f'用户: {username}, ID: {user_id}'
@app.route('/clear_session')
def clear_session():
# 清除整个会话
session.clear()
return 'Session 已清除'
关键点:
session对象的操作就像一个字典。- 必须设置
app.config['SECRET_KEY'],否则会抛出异常。 - 默认情况下,session cookie 在浏览器关闭后失效。要使其持久化,设置
session.permanent = True,并配置PERMANENT_SESSION_LIFETIME。
六、高级功能 - 蓝图与钩子
当应用规模增长时,需要更高级的组织方式和执行机制。
6.1 蓝图 - 模块化应用
蓝图是组织一组相关视图和代码的方式。它们不是独立的应用,但可以在应用注册后,以应用的一部分来运行。这对于大型项目或团队协作至关重要。
项目结构示例:
/myapp
/app
__init__.py
/auth
__init__.py
routes.py
/blog
__init__.py
routes.py
run.py
app/auth/__init__.py (定义蓝图):
python
from flask import Blueprint
# 创建一个蓝图实例
# 第一个参数是蓝图的名字
# 第二个参数是 __name__,用于定位蓝图资源
# url_prefix 会给该蓝图下的所有路由加上前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
from . import routes # 导入路由,避免循环依赖
app/auth/routes.py (在蓝图上定义路由):
python
from flask import render_template
from . import auth_bp
@auth_bp.route('/login')
def login():
return render_template('auth/login.html')
@auth_bp.route('/register')
def register():
return render_template('auth/register.html')
app/__init__.py (注册蓝图):
python
from flask import Flask
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'a-very-secret-key'
# ... 其他配置 ...
# 注册蓝图
from .auth import auth_bp
app.register_blueprint(auth_bp)
from .blog import blog_bp
app.register_blueprint(blog_bp)
return app
run.py (启动应用):
python
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
现在,登录页面的 URL 就变成了 /auth/login。
6.2 请求钩子
请求钩子是在处理请求之前或之后执行的函数,非常适合用于权限验证、打开数据库连接、性能监控等场景。
@before_request: 在每个请求之前执行。如果返回了响应,则直接停止,不再执行视图函数。@before_first_request: 在处理第一个 请求之前执行。只执行一次。可用于初始化操作。@after_request: 在每个请求之后执行,前提是没有未处理的异常。它需要接收并返回响应对象。@teardown_request: 在每个请求之后执行,无论是否出现异常。它接收异常对象作为参数。
python
@app.before_request
def before_request():
# 检查用户是否登录
if 'user_id' not in session and request.endpoint not in ['login', 'static']:
return redirect(url_for('login'))
@app.after_request
def after_request(response):
# 在响应头中添加一个自定义头
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
return response
@app.teardown_request
def teardown_request(exception):
# 关闭数据库连接等清理工作
db.close()
七、Flask 扩展生态系统
Flask 的"微"魅力在于其丰富的扩展。以下是一些最常用和最重要的扩展。
7.1 Flask-SQLAlchemy - 数据库 ORM
SQLAlchemy 是 Python 中最强大的 ORM 框架。Flask-SQLAlchemy 为其提供了 Flask 集成。
安装 : pip install Flask-SQLAlchemy
配置与使用:
python
from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 关闭不必要的信号
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
# 在视图或命令行中使用
# db.create_all() # 创建表
# user = User(username='test', email='test@example.com')
# db.session.add(user)
# db.session.commit()
# User.query.all()
# User.query.filter_by(username='test').first()
7.2 Flask-Migrate - 数据库迁移
在开发过程中,数据库模型经常变化。Flask-Migrate(基于 Alembic)可以管理数据库结构的版本控制,实现平滑升级和降级。
安装 : pip install Flask-Migrate
使用:
python
from flask_migrate import Migrate
# ... 在 create_app() 中 ...
db = SQLAlchemy(app)
migrate = Migrate(app, db)
命令行操作:
bash
# 初始化迁移环境 (只需一次)
flask db init
# 生成迁移脚本 (检测模型变化)
flask db migrate -m "Initial migration."
# 应用迁移到数据库
flask db upgrade
7.3 Flask-WTF - 表单处理与 CSRF 防护
Web 表单处理繁琐且容易出错。Flask-WTF 集成了 WTForms 库,简化了表单定义、验证和渲染,并内置了 CSRF 防护。
安装 : pip install Flask-WTF
使用:
python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length
class LoginForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(min=4, max=20)])
password = PasswordField('密码', validators=[DataRequired()])
submit = SubmitField('登录')
# 在视图中
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit(): # 验证表单数据
# 登录逻辑
return redirect(url_for('index'))
return render_template('login.html', title='登录', form=form)
模板渲染 (login.html):
html
{% from "bootstrap/wtf.html" import render_field %} <!-- 如果使用Flask-Bootstrap -->
<form method="POST" action="">
{{ form.hidden_tag() }} <!-- CSRF Token -->
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ form.submit() }}
</form>
八、部署
开发服务器 (app.run()) 性能差且不稳定,绝不能用于生产环境。生产部署通常使用专业的 WSGI 服务器,如 Gunicorn 或 uWSGI,配合反向代理 Nginx。
8.1 WSGI的方式
WSGI (Web Server Gateway Interface) 是 Python Web 应用和 Web 服务器之间的通用接口标准。Flask 应用就是一个 WSGI 应用。
8.2 Gunicorn的方式
一个流行的 Python WSGI HTTP 服务器。
安装 : pip install gunicorn
运行:
bash
# -w 4 表示启动 4 个 worker 进程
# -b 0.0.0.0:8000 表示绑定到所有网络接口的 8000 端口
# myproject:app 是 模块名:Flask实例名
gunicorn -w 4 -b 0.0.0.0:8000 myproject:app
8.3 Nginx的方式
作为反向代理,Nginx 可以处理静态文件、负载均衡、SSL 终止和 HTTP 缓存,将动态请求转发给 Gunicorn。
Nginx 配置示例 (/etc/nginx/sites-available/myproject):
nginx
server {
listen 80;
server_name your_domain.com;
location /static {
alias /path/to/your/project/static; # 静态文件路径
}
location / {
proxy_pass http://127.0.0.1:8000; # 转发给 Gunicorn
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}