Flask 作为 Python 生态中最受欢迎的轻量级 Web 框架,其设计哲学体现了"微内核+可扩展"的现代框架理念。与 Django 这类"大而全"的框架不同,Flask 仅内置了 Werkzeug WSGI 工具库和 Jinja2 模板引擎这两个核心组件,开发者可以根据项目需求自由组合扩展。这种设计带来三个显著优势:
实际开发中,Flask 特别适合以下场景:
- 快速原型开发(MVP)
- 微服务架构中的单个服务
- 需要深度定制技术栈的项目
- 作为 API 服务器配合前端框架使用
Flask 的请求处理流程体现了经典的 WSGI 应用模式:
python复制# 典型 Flask 应用的最小实现
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
class MiniFlask:
def __init__(self):
self.url_map = Map([
Rule('/', endpoint='index'),
Rule('/user/<username>', endpoint='user')
])
def dispatch_request(self, request):
adapter = self.url_map.bind_to_environ(request.environ)
endpoint, values = adapter.match()
return getattr(self, f'on_{endpoint}')(request, **values)
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request)
return response(environ, start_response)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
这个简化实现展示了 Flask 最核心的路由和 WSGI 处理机制。实际开发中我们当然不需要自己实现这些,但理解底层原理有助于更好地使用框架。
虽然 Flask 支持 Python 3.6+,但不同版本有重要差异:
| Python 版本 | 对 Flask 的影响 | 建议 |
|---|---|---|
| 3.6-3.7 | 兼容但已停止维护 | 不推荐 |
| 3.8 | 完全兼容,性能提升 | 稳定项目推荐 |
| 3.9+ | 支持新语法特性 | 新项目首选 |
推荐使用 pyenv 管理多版本 Python:
bash复制# 安装最新 Python 3.9
pyenv install 3.9.12
pyenv global 3.9.12
# 验证版本
python -V # 应显示 3.9.12
永远不要在系统 Python 中直接安装包!虚拟环境是 Python 开发的标配:
bash复制# 创建项目目录
mkdir flask-demo && cd flask-demo
# 创建虚拟环境
python -m venv venv
# 激活环境 (Linux/macOS)
source venv/bin/activate
# Windows 系统
venv\Scripts\activate
激活后,命令行提示符前会出现 (venv) 标记。此时所有 pip 安装的包都会隔离在当前环境中。
生产级项目应该使用 requirements.txt 结合 pip-tools:
code复制Flask==2.0.2
flask-sqlalchemy
bash复制pip install pip-tools
pip-compile requirements.in
这会生成包含所有传递依赖的 requirements.txt,示例输出:
code复制#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile requirements.in
#
click==8.0.3
# via flask
flask==2.0.2
# via -r requirements.in
flask-sqlalchemy==2.5.1
# via -r requirements.in
...
bash复制pip install -r requirements.txt
这种方式确保开发、测试、生产环境的一致性,是团队协作的基础。
小型项目可以采用模块式结构:
code复制/flask-demo
/venv # 虚拟环境
/static # 静态资源
/css
/js
/images
/templates # Jinja2 模板
app.py # 主应用文件
config.py # 配置
requirements.txt # 依赖
中大型项目建议使用包式结构:
code复制/flask-project
/instance # 实例特定配置
config.py
/myapp
/static
/templates
/blueprints # 蓝图模块
auth.py
blog.py
__init__.py # 应用工厂
models.py # 数据模型
views.py # 视图函数
tests/ # 测试代码
pyproject.toml # 项目元数据
README.md
Flask 配置可以通过多种方式加载,推荐分层配置模式:
python复制# config.py
import os
from dotenv import load_dotenv
load_dotenv() # 加载 .env 文件
class Config:
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-fallback-key')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL')
config = {
'development': DevelopmentConfig,
'production': ProductionConfig
}
使用时:
python复制from flask import Flask
from config import config
def create_app(config_name='development'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# 实例文件夹配置会覆盖类配置
app.config.from_pyfile('config.py', silent=True)
return app
关键技巧:
.env 文件管理变量Flask 的路由系统基于 Werkzeug 的 routing 模块,支持多种注册方式:
python复制# 装饰器语法(最常用)
@app.route('/')
def index():
return "Hello"
# 集中注册(适合大型应用)
def user_profile(user_id):
return f"User {user_id}"
app.add_url_rule('/user/<int:user_id>', 'user', user_profile)
# 基于类视图(RESTful API 常用)
from flask.views import MethodView
class UserAPI(MethodView):
def get(self, user_id):
return f"GET user {user_id}"
def post(self):
return "Create user"
app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))
app.add_url_rule('/users/<int:user_id>', view_func=UserAPI.as_view('user'))
Flask 支持多种参数类型转换:
| 类型转换器 | 说明 | 示例 |
|---|---|---|
| string | 默认类型,接受不带斜线的文本 | /path/<string:name> |
| int | 只接受正整数 | /post/<int:post_id> |
| float | 接受浮点数 | /price/<float:amount> |
| path | 类似 string 但接受斜线 | /file/<path:filepath> |
| uuid | 接受 UUID 字符串 | /resource/<uuid:uid> |
自定义转换器示例:
python复制from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
def __init__(self, url_map, *items):
super().__init__(url_map)
self.regex = items[0]
app.url_map.converters['re'] = RegexConverter
@app.route('/user/<re(r'[a-z]{3,8}'):username>')
def user_profile(username):
return f"Username: {username}"
python复制# 反例:低效的路由设计
@app.route('/articles/<int:id>')
def get_article(id):
pass
@app.route('/articles/latest') # 这个路由永远不会被匹配到
def latest_article():
pass
# 正解:特定路由优先
@app.route('/articles/latest')
def latest_article():
pass
@app.route('/articles/<int:id>')
def get_article(id):
pass
Flask 的 request 对象封装了 HTTP 请求的所有信息:
python复制from flask import request
@app.route('/api', methods=['GET', 'POST', 'PUT'])
def handle_request():
# 请求方法
method = request.method
# 查询参数 (GET)
page = request.args.get('page', default=1, type=int)
# 表单数据 (POST)
username = request.form.get('username')
# JSON 数据 (Content-Type: application/json)
json_data = request.get_json()
# 文件上传
uploaded_file = request.files.get('avatar')
if uploaded_file:
uploaded_file.save(f'uploads/{uploaded_file.filename}')
# 请求头
user_agent = request.headers.get('User-Agent')
# Cookies
session_id = request.cookies.get('session_id')
return f"Handling {method} request"
Flask 视图可以返回多种响应形式:
python复制from flask import make_response, jsonify, redirect, url_for
# 1. 简单字符串
@app.route('/text')
def text_response():
return "Plain text response"
# 2. 自定义状态码
@app.route('/created')
def created_response():
return "Resource created", 201
# 3. 响应对象
@app.route('/custom')
def custom_response():
response = make_response("Custom response")
response.headers['X-Custom-Header'] = 'Value'
response.set_cookie('visited', 'true')
return response
# 4. JSON 响应
@app.route('/api/data')
def json_response():
return jsonify({'status': 'success', 'data': [1, 2, 3]})
# 5. 重定向
@app.route('/old')
def old_endpoint():
return redirect(url_for('new_endpoint'))
# 6. 流式响应
@app.route('/stream')
def stream_response():
def generate():
yield "Hello "
yield "World!"
return Response(generate(), mimetype='text/plain')
Flask 使用上下文局部变量(context locals)实现请求隔离:
python复制from flask import current_app, g
@app.before_request
def before_request():
g.db = connect_to_database() # 每个请求独立的数据库连接
g.user = get_current_user()
@app.teardown_request
def teardown_request(exception=None):
db = getattr(g, 'db', None)
if db is not None:
db.close()
@app.route('/')
def index():
app_name = current_app.name # 访问应用上下文
return f"Welcome to {app_name}"
关键点:
current_app:指向处理当前请求的应用实例g:请求生命周期内的临时存储对象request:当前请求对象session:用户会话存储合理的模板结构示例:
code复制templates/
base.html # 基础模板
layouts/
admin.html # 管理后台布局
public.html # 前台布局
includes/
header.html # 页头片段
footer.html # 页脚片段
pages/
home.html # 首页
about.html # 关于页
base.html 示例:
html复制<!DOCTYPE html>
<html lang="zh-CN">
<head>
{% block head %}
<meta charset="UTF-8">
<title>{% block title %}{% endblock %} - My Site</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% endblock %}
</head>
<body>
{% include 'includes/header.html' %}
<div class="content">
{% block content %}{% endblock %}
</div>
{% include 'includes/footer.html' %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% endblock %}
</body>
</html>
子模板示例 (home.html):
html复制{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block head %}
{{ super() }} {# 保留父模板内容 #}
<style>
.hero { background: #f5f5f5; }
</style>
{% endblock %}
{% block content %}
<div class="hero">
<h1>欢迎来到我的网站</h1>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
console.log('Home page loaded');
</script>
{% endblock %}
扩展 Jinja2 功能:
python复制# 注册过滤器
@app.template_filter('reverse')
def reverse_filter(s):
return s[::-1]
# 注册全局函数
@app.context_processor
def utility_processor():
def format_price(amount, currency='¥'):
return f"{currency}{amount:,.2f}"
return {'format_price': format_price}
模板中使用:
html复制<p>{{ "hello"|reverse }}</p>
<p>{{ format_price(1234.5) }}</p>
开启模板缓存(生产环境默认开启):
python复制app.config['TEMPLATES_AUTO_RELOAD'] = False # 生产环境设为 False
合理使用模板片段缓存:
html复制{% cache 300, "sidebar" %}
<div class="sidebar">
{% include "widgets/recent_posts.html" %}
{% include "widgets/tag_cloud.html" %}
</div>
{% endcache %}
需要安装 Flask-Caching 扩展。
避免在模板中进行复杂计算:将数据处理逻辑放在视图函数中
| 扩展名称 | 用途 | 安装命令 |
|---|---|---|
| Flask-SQLAlchemy | ORM 集成 | pip install flask-sqlalchemy |
| Flask-Migrate | 数据库迁移 | pip install flask-migrate |
| Flask-WTF | 表单处理 | pip install flask-wtf |
| Flask-Login | 用户认证 | pip install flask-login |
| Flask-Caching | 缓存支持 | pip install flask-caching |
| Flask-Mail | 邮件发送 | pip install flask-mail |
| Flask-RESTful | REST API | pip install flask-restful |
SQLAlchemy 配置示例:
python复制from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://user:pass@localhost/dbname'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
migrate.init_app(app, db)
return app
模型定义示例:
python复制class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
数据库迁移流程:
bash复制# 初始化迁移仓库(只需执行一次)
flask db init
# 生成迁移脚本
flask db migrate -m "create user and post tables"
# 应用迁移
flask db upgrade
基于 Flask-Login 的认证示例:
python复制from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(UserMixin, db.Model):
# ... 模型定义同上 ...
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.query.filter_by(username=request.form['username']).first()
if user and check_password(user.password, request.form['password']):
login_user(user)
return redirect(url_for('dashboard'))
return render_template('auth/login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('home'))
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html')
密码安全处理:
python复制from werkzeug.security import generate_password_hash, check_password_hash
# 注册时保存哈希值
hashed_pw = generate_password_hash('plain_password')
# 验证密码
check_password_hash(hashed_pw, 'input_password') # 返回布尔值
| 服务器 | 特点 | 适用场景 |
|---|---|---|
| Gunicorn | 简单可靠,预加载模型 | 常规应用 |
| uWSGI | 功能丰富,性能优异 | 复杂应用 |
| Waitress | 纯 Python,易于配置 | Windows 环境 |
| mod_wsgi | Apache 集成 | 已有 Apache 环境 |
Gunicorn 基本用法:
bash复制# 安装
pip install gunicorn
# 运行(4个工作进程)
gunicorn -w 4 -b 127.0.0.1:8000 app:app
# 生产推荐配置
gunicorn \
--workers 4 \
--threads 2 \
--bind unix:myapp.sock \
--worker-class gevent \
--timeout 120 \
--access-logfile - \
--error-logfile - \
--preload \
app:app
典型 Nginx 配置:
nginx复制upstream flask_app {
server unix:/path/to/myapp.sock;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://flask_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static {
alias /path/to/static/files;
expires 30d;
}
}
使用 Fabric 实现一键部署:
python复制# fabfile.py
from fabric import task
@task
def deploy(c):
# 更新代码
c.run("cd /path/to/app && git pull")
# 安装依赖
c.run("cd /path/to/app && venv/bin/pip install -r requirements.txt")
# 数据库迁移
c.run("cd /path/to/app && venv/bin/flask db upgrade")
# 重启服务
c.run("sudo systemctl restart myapp.service")
# 清理旧版本
c.run("cd /path/to/app && venv/bin/pip cache purge")
使用方式:
bash复制fab -H user@server deploy
Flask-DebugToolbar:开发时实时查看性能数据
python复制from flask_debugtoolbar import DebugToolbarExtension
app.config['DEBUG_TB_INTERCEPT_REDIRECTS'] = False
toolbar = DebugToolbarExtension(app)
cProfile:Python 内置性能分析
bash复制python -m cProfile -o profile.prof app.py
Flask-Profiler:生产环境友好的性能监控
python复制from flask_profiler import Profiler
profiler = Profiler()
profiler.init_app(app)
数据库查询 N+1 问题:
python复制# 反例:每次迭代都查询数据库
users = User.query.all()
for user in users:
print(user.posts.all()) # 每次循环都执行查询
# 正解:使用 joinedload 预加载
from sqlalchemy.orm import joinedload
users = User.query.options(joinedload(User.posts)).all()
模板渲染过慢:
静态资源未压缩:
交互式调试器:
python复制@app.route('/debug')
def debug_demo():
breakpoint() # Python 3.7+ 内置断点
return "Debugging"
日志配置:
python复制import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)
handler.setLevel(logging.INFO)
app.logger.addHandler(handler)
@app.route('/log')
def log_demo():
app.logger.info('This is an info message')
app.logger.error('Something went wrong', exc_info=True)
return "Check your logs"
请求追踪:
python复制@app.after_request
def after_request(response):
app.logger.info(
f"{request.remote_addr} {request.method} {request.path} "
f"{response.status_code} {request.user_agent}"
)
return response
Flask 应用的测试应该包含多个层次:
| 工具 | 用途 | 示例 |
|---|---|---|
| pytest | 测试框架 | pip install pytest |
| pytest-cov | 覆盖率报告 | pip install pytest-cov |
| factory-boy | 测试数据工厂 | pip install factory-boy |
| selenium | 浏览器自动化 | pip install selenium |
基础测试示例:
python复制# test_app.py
import pytest
from app import create_app
@pytest.fixture
def client():
app = create_app({'TESTING': True})
with app.test_client() as client:
yield client
def test_home_page(client):
response = client.get('/')
assert response.status_code == 200
assert b"Welcome" in response.data
def test_login(client):
response = client.post('/login', data={
'username': 'test',
'password': 'secret'
}, follow_redirects=True)
assert b"Dashboard" in response.data
运行测试:
bash复制pytest -v --cov=app tests/
GitHub Actions 示例配置 (.github/workflows/test.yml):
yaml复制name: Python CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest -v --cov=app tests/
- name: Upload coverage
uses: codecov/codecov-action@v1
| 威胁类型 | 防护措施 | Flask 实现 |
|---|---|---|
| SQL 注入 | 使用 ORM/参数化查询 | SQLAlchemy 自动防护 |
| XSS | 模板自动转义 | Jinja2 默认开启 |
| CSRF | 表单令牌验证 | Flask-WTF 提供 |
| 点击劫持 | 设置 HTTP 头 | Flask-Talisman |
| 敏感数据泄露 | 配置安全头 | Flask-Security |
python复制from flask_talisman import Talisman
def create_app():
app = Flask(__name__)
# 基础安全配置
app.config.update(
SECRET_KEY=os.getenv('SECRET_KEY'),
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
PERMANENT_SESSION_LIFETIME=timedelta(days=1),
)
# 安全头设置
Talisman(
app,
force_https=True,
strict_transport_security=True,
session_cookie_secure=True,
content_security_policy={
'default-src': "'self'",
'script-src': ["'self'", 'cdn.example.com'],
'style-src': ["'self'", "'unsafe-inline'"],
}
)
return app
使用 Argon2 算法(比 PBKDF2 更安全):
python复制from argon2 import PasswordHasher
ph = PasswordHasher()
hashed_pw = ph.hash("password")
try:
ph.verify(hashed_pw, "input_password")
except:
# 验证失败
定期更新哈希参数:
python复制# 检查是否需要重新哈希
if ph.check_needs_rehash(hashed_pw):
new_hash = ph.hash("password")
密码强度验证:
python复制import zxcvbn
result = zxcvbn.zxcvbn("password")
if result['score'] < 3:
raise ValueError("密码强度不足")
Flask 官方推荐的 cookiecutter 模板:
bash复制pip install cookiecutter
cookiecutter https://github.com/cookiecutter-flask/cookiecutter-flask
扩展 Flask 命令行工具:
python复制import click
from flask.cli import AppGroup
db_cli = AppGroup('db')
@db_cli.command('seed')
@click.argument('filename')
def seed_data(filename):
"""从文件导入初始数据"""
# 实现数据导入逻辑
print(f"Seeding data from {filename}")
app.cli.add_command(db_cli)
使用方式:
bash复制flask db seed data.json
添加健康检查端点:
python复制@app.route('/health')
def health_check():
from sqlalchemy import text
try:
db.session.execute(text('SELECT 1'))
return jsonify({'status': 'healthy'}), 200
except Exception as e:
return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
结合监控系统使用:
bash复制# Prometheus 监控示例
curl -o /dev/null -s -w "%{http_code}\n" http://localhost:5000/health
Flask-RESTful 示例:
python复制from flask_restful import Api, Resource
api = Api(app)
class UserAPI(Resource):
def get(self, user_id):
user = User.query.get_or_404(user_id)
return {'username': user.username, 'email': user.email}
def put(self, user_id):
user = User.query.get_or_404(user_id)
data = request.get_json()
user.email = data['email']
db.session.commit()
return {'message': 'User updated'}
api.add_resource(UserAPI, '/api/users/<int:user_id>')
JWT 认证实现:
python复制from flask_jwt_extended import JWTManager, create_access_token, jwt_required
app.config['JWT_SECRET_KEY'] = 'super-secret'
jwt = JWTManager(app)
@app.route('/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
access_token = create_access_token(identity=username)
return {'access_token': access_token}
return {'error': 'Invalid credentials'}, 401
@app.route('/protected')
@jwt_required()
def protected():
return {'message': 'This is protected content'}
使用 OpenAPI/Swagger:
python复制from flask_swagger_ui import get_swaggerui_blueprint
SWAGGER_URL = '/api/docs'
API_URL = '/static/swagger.json'
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
API_URL,
config={'app_name': "My API"}
)
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
配置 Celery:
python复制from celery import Celery
def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['CELERY_BROKER_URL']
)
celery.conf.update(app.config)
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
return celery
celery = make_celery(app)
定义任务:
python复制@celery.task
def send_async_email(email_data):
from flask_mail import Message
mail = Mail(current_app)
msg = Message(
subject=email_data['subject'],
recipients=[email_data['to']],
body=email_data['body']
)
mail.send(msg)
调用任务:
python复制@app.route('/send-mail')
def send_mail():
email_data = {
'subject': 'Hello',
'to': 'user@example.com',
'body': 'This is a test email'
}
send_async_email.delay(email_data)
return "Email is being sent"
使用 Flower 监控 Celery:
bash复制pip install flower
celery -A app.celery flower --port=5555
访问 http://localhost:5555 查看任务状态。
python复制from flask_babel import Babel, _
app.config['BABEL_DEFAULT_LOCALE'] = 'zh'
app.config['BABEL_TRANSLATION_DIRECTORIES'] = '../translations'
babel = Babel(app)
@babel.localeselector
def get_locale():
return request.accept_languages.best_match(['zh', 'en'])
@app.route('/')
def home():
return _("Hello World")
bash复制pybabel extract -F babel.cfg -o messages.pot .
bash复制pybabel init -i messages.pot -d translations -l zh
编辑 translations/zh/LC_MESSAGES/messages.po
编译翻译:
bash复制pybabel compile -d translations
python复制class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)