外观
Flask 笔记
约 2500 字大约 8 分钟
2025-08-16
一、Flask 基础
1.1 什么是 Flask?
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它由 Armin Ronacher 于 2010 年开发,基于 Werkzeug WSGI 工具箱和 Jinja2 模板引擎。Flask 的设计哲学是"微框架",只提供 Web 开发的核心功能,其他功能通过扩展实现。
Flask 的核心优势:
- 轻量级:核心功能简洁,没有强制性的项目结构
- 灵活性:可根据需要选择扩展,不强制使用特定工具
- 易学易用:API 设计简洁,学习曲线平缓
- 强大的扩展生态系统:有丰富的官方和社区扩展
- 适合小型到中型应用:快速开发原型和小型项目
1.2 安装与项目创建
安装 Flask
pip install flask创建第一个 Flask 应用
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)运行应用
python app.py项目结构概览
my-flask-app/
├── app/
│ ├── __init__.py # Flask 应用工厂
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── main.py # 主路由
│ │ └── auth.py # 认证路由
│ ├── templates/ # 模板文件
│ ├── static/ # 静态文件
│ └── models.py # 数据模型
├── config.py # 配置文件
├── requirements.txt # 依赖列表
└── run.py # 启动脚本1.3 配置管理
基本配置方式
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'your-secret-key'从对象加载配置
# config.py
class Config:
SECRET_KEY = 'your-secret-key'
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
# app/__init__.py
from flask import Flask
from config import DevelopmentConfig
def create_app(config_class=DevelopmentConfig):
app = Flask(__name__)
app.config.from_object(config_class)
# 初始化扩展
# ...
# 注册蓝图
# ...
return app使用环境变量
# config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'fallback-secret-key'二、路由与视图
2.1 基本路由
定义路由
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '首页'
@app.route('/about')
def about():
return '关于页面'HTTP 方法支持
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()2.2 动态路由
变量规则
@app.route('/user/<username>')
def show_user_profile(username):
return f'用户: {username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'帖子ID: {post_id}'支持的转换器:
string:(默认)接受任何不包含斜杠的文本int:接受正整数float:接受正浮点数path:接受包含斜杠的字符串uuid:接受UUID字符串
2.3 URL 构建
url_for 函数
from flask import url_for
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
with app.test_request_context():
print(url_for('index')) # /
print(url_for('login')) # /login
print(url_for('login', next='/')) # /login?next=/
print(url_for('profile', username='John Doe')) # /user/John%20Doe在模板中使用
<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('user', username=user.username) }}">用户资料</a>三、请求与响应处理
3.1 请求对象
获取请求数据
from flask import request
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
# 或
username = request.args.get('username') # 查询参数
file = request.files['file'] # 上传文件
return '登录成功'常用请求属性:
request.args:查询字符串参数(GET 请求)request.form:表单数据(POST 请求)request.values:合并了 args 和 formrequest.cookies:客户端 cookiesrequest.headers:请求头request.files:上传的文件request.method:请求方法(GET、POST 等)request.url:完整的请求 URL
3.2 响应对象
基本响应
from flask import jsonify, make_response
@app.route('/api/users')
def get_users():
users = [{'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Jane'}]
return jsonify(users) # 返回 JSON 响应
@app.route('/custom-response')
def custom_response():
response = make_response('<h1>自定义响应</h1>', 200)
response.headers['X-Custom-Header'] = 'value'
return response重定向与错误响应
from flask import redirect, url_for, abort
@app.route('/admin')
def admin():
if not current_user.is_admin:
abort(403) # 禁止访问
return '管理员页面'
@app.route('/old-page')
def old_page():
return redirect(url_for('new_page'))3.3 模板渲染
使用 Jinja2 模板
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)模板示例 (hello.html)
<!doctype html>
<html>
<body>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
</body>
</html>模板继承
{# base.html #}
<!doctype html>
<html>
<head>
{% block head %}
<title>{% block title %}默认标题{% endblock %}</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
</body>
</html>
{# index.html #}
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block content %}
<h1>欢迎来到首页</h1>
<p>这是内容。</p>
{% endblock %}四、表单处理
4.1 使用 Flask-WTF
安装扩展
pip install flask-wtf创建表单类
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email
class LoginForm(FlaskForm):
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
remember_me = BooleanField('记住我')
submit = SubmitField('登录')在视图中使用表单
from flask import render_template, redirect, url_for
from .forms import LoginForm
@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', form=form)模板中渲染表单
<form method="POST" action="{{ url_for('login') }}">
{{ form.hidden_tag() }}
<div>
{{ form.email.label }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</div>
<div>
{{ form.password.label }}
{{ form.password(class="form-control") }}
</div>
<div>
{{ form.remember_me() }} {{ form.remember_me.label }}
</div>
<button type="submit">登录</button>
</form>4.2 文件上传
配置上传目录
import os
from flask import Flask
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), 'uploads')
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB处理文件上传
from flask import request, redirect, url_for
from werkzeug.utils import secure_filename
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# 检查是否有文件部分
if 'file' not in request.files:
return '没有文件部分'
file = request.files['file']
# 如果用户没有选择文件,浏览器也会提交一个空的文件名
if file.filename == '':
return '没有选择文件'
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
return '''
<!doctype html>
<title>上传新文件</title>
<h1>上传新文件</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=上传>
</form>
'''五、数据库操作
5.1 使用 SQLAlchemy
安装扩展
pip install flask-sqlalchemy配置数据库
# config.py
import os
class Config:
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False初始化扩展
# app/__init__.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
# 其他初始化...
return app5.2 定义模型
创建数据模型
# app/models.py
from . import db
from datetime import datetime
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __repr__(self):
return f'<User {self.username}>'
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return f'<Post {self.body}>'5.3 数据库操作
创建数据库
# 初始化数据库
from app import create_app, db
app = create_app()
with app.app_context():
db.create_all()CRUD 操作
# 创建
user = User(username='john', email='john@example.com')
db.session.add(user)
db.session.commit()
# 查询
user = User.query.filter_by(username='john').first()
users = User.query.order_by(User.username).all()
# 更新
user.username = 'jdoe'
db.session.commit()
# 删除
db.session.delete(user)
db.session.commit()5.4 数据库迁移
安装 Flask-Migrate
pip install flask-migrate初始化迁移仓库
flask db init创建迁移脚本
flask db migrate -m "users table"应用迁移
flask db upgrade在应用中集成
# app/__init__.py
from flask_migrate import Migrate
migrate = Migrate()
def create_app(config_class=Config):
# ...
migrate.init_app(app, db)
return app六、用户认证与授权
6.1 使用 Flask-Login
安装扩展
pip install flask-login初始化扩展
# app/__init__.py
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.login_message = '请登录以访问此页面。'
def create_app(config_class=Config):
# ...
login_manager.init_app(app)
return app用户模型实现
# app/models.py
from . import db, login_manager
from flask_login import UserMixin
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(UserMixin, db.Model):
# 与之前相同...登录视图
# app/auth/routes.py
from flask import render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required
from . import bp
from .forms import LoginForm
from ..models import User
@bp.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None or not user.check_password(form.password.data):
flash('无效的邮箱或密码')
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('main.index'))
return render_template('auth/login.html', form=form)
@bp.route('/logout')
def logout():
logout_user()
return redirect(url_for('main.index'))6.2 保护路由
使用 login_required 装饰器
from flask_login import login_required
@app.route('/profile')
@login_required
def profile():
return '只有登录用户才能看到此页面'在模板中检查登录状态
{% if current_user.is_authenticated %}
<p>你好, {{ current_user.username }}!</p>
<a href="{{ url_for('auth.logout') }}">登出</a>
{% else %}
<a href="{{ url_for('auth.login') }}">登录</a>
{% endif %}七、蓝图 (Blueprints)
7.1 创建蓝图
定义蓝图
# app/main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__)
from . import routes, errors注册路由
# app/main/routes.py
from . import main
@main.route('/')
def index():
return '主页'
@main.route('/about')
def about():
return '关于页面'7.2 注册蓝图
在应用工厂中注册
# app/__init__.py
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
# 注册蓝图
from app.main import main as main_blueprint
app.register_blueprint(main_blueprint)
from app.auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth')
return app蓝图的优势:
- 模块化应用结构
- 可以设置 URL 前缀
- 可以设置模板和静态文件路径
- 便于大型应用的组织
八、错误处理
8.1 自定义错误页面
创建错误处理函数
# app/errors/handlers.py
from flask import render_template
from . import bp
@bp.app_errorhandler(404)
def not_found_error(error):
return render_template('errors/404.html'), 404
@bp.app_errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('errors/500.html'), 500注册错误蓝图
# app/__init__.py
def create_app(config_class=Config):
# ...
from app.errors import bp as errors_bp
app.register_blueprint(errors_bp)
return app8.2 自定义异常
创建自定义异常
# app/exceptions.py
class InvalidUsage(Exception):
status_code = 400
def __init__(self, message, status_code=None, payload=None):
super().__init__()
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
return rv注册异常处理程序
# app/__init__.py
from .exceptions import InvalidUsage
def create_app(config_class=Config):
# ...
@app.errorhandler(InvalidUsage)
def handle_invalid_usage(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
return app九、实用技巧与最佳实践
9.1 项目结构组织
推荐的项目结构
my-flask-app/
├── app/
│ ├── __init__.py
│ ├── models/
│ │ └── __init__.py
│ ├── routes/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── auth.py
│ │ └── api.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── index.html
│ │ └── auth/
│ │ └── login.html
│ ├── static/
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── forms.py
│ ├── errors.py
│ └── utils.py
├── config.py
├── requirements.txt
└── run.py9.2 RESTful API 设计
使用 Flask-RESTful
pip install flask-restful创建 API 资源
# app/api/resources.py
from flask_restful import Resource, reqparse
from ..models import User
parser = reqparse.RequestParser()
parser.add_argument('username', type=str, required=True, help="Username is required")
parser.add_argument('email', type=str, required=True, help="Email is required")
class UserResource(Resource):
def get(self, user_id):
user = User.query.get_or_404(user_id)
return {'id': user.id, 'username': user.username, 'email': user.email}
def put(self, user_id):
args = parser.parse_args()
user = User.query.get_or_404(user_id)
user.username = args['username']
user.email = args['email']
db.session.commit()
return {'id': user.id, 'username': user.username, 'email': user.email}, 200
class UserListResource(Resource):
def get(self):
users = User.query.all()
return [{'id': user.id, 'username': user.username} for user in users]
def post(self):
args = parser.parse_args()
user = User(username=args['username'], email=args['email'])
db.session.add(user)
db.session.commit()
return {'id': user.id, 'username': user.username}, 201注册 API
# app/api/__init__.py
from flask import Blueprint
from flask_restful import Api
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
from .resources import UserResource, UserListResource
def init_api(app):
api.add_resource(UserListResource, '/users')
api.add_resource(UserResource, '/users/<int:user_id>')
app.register_blueprint(api_bp, url_prefix='/api')9.3 部署准备
生产环境配置
# config.py
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
# 使用真正的密钥
SECRET_KEY = os.environ.get('SECRET_KEY')
# 配置邮件服务器
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
# 分页设置
POSTS_PER_PAGE = 25使用 Gunicorn 部署
# 安装
pip install gunicorn
# 运行
gunicorn -w 4 -b 0.0.0.0:5000 run:app使用 Nginx 作为反向代理
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}