目录

Python Flask 框架入门

学习目标:掌握 Flask 微框架的核心概念,理解其灵活、轻量的设计理念,能够构建包含路由、模板、数据库的 Web 应用。


1. Flask 简介

Flask 是一个轻量级的 Python Web 框架,被称为 “微框架”,因为它核心精简但可通过扩展灵活增强。

1.1 设计哲学

  • 核心精简:只提供路由、请求处理、模板等基础功能
  • 灵活扩展:通过扩展包添加数据库、认证等功能
  • 自由组合:开发者可选择最适合的组件

1.2 Flask vs Django

特性 Flask Django
定位 微框架 全栈框架
组件 按需添加 内置电池
学习曲线 平缓 较陡
数据库 自由选择(SQLAlchemy 等) 内置 ORM
模板 Jinja2(内置) Django 模板
管理后台 需扩展 内置
适用场景 API、微服务、小型项目 大型应用、快速开发

2. 快速开始

2.1 安装与基础应用

# 安装 Flask
pip install flask

# 可选:安装常用扩展
pip install flask-sqlalchemy flask-wtf flask-login
# app.py - 最小的 Flask 应用
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello Flask!'

if __name__ == '__main__':
    app.run(debug=True, port=5000)

2.2 运行应用

# 方式1:直接运行
python app.py

# 方式2:使用环境变量
export FLASK_APP=app.py
export FLASK_ENV=development  # 开发模式(自动重载、调试信息)
flask run --host=0.0.0.0 --port=5000

# 方式3:生产环境(使用 Gunicorn)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app

3. 路由系统

3.1 基本路由

from flask import Flask

app = Flask(__name__)

# 静态路由
@app.route('/')
def index():
    return '首页'

@app.route('/about')
def about():
    return '关于我们'

# 动态路由 - 变量规则
@app.route('/user/<username>')
def show_user(username):
    return f'用户: {username}'

# 指定类型
@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'文章 ID: {post_id}, 类型: {type(post_id).__name__}'

@app.route('/price/<float:amount>')
def show_price(amount):
    return f'价格: ¥{amount:.2f}'

@app.route('/path/<path:subpath>')
def show_path(subpath):
    return f'路径: {subpath}'

# HTTP 方法
@app.route('/api/data', methods=['GET', 'POST'])
def handle_data():
    from flask import request
    
    if request.method == 'POST':
        return '创建数据'
    return '获取数据'

3.2 URL 构建

from flask import url_for, redirect

@app.route('/login')
def login():
    return '登录页'

@app.route('/profile/<username>')
def profile(username):
    return f'{username} 的个人中心'

with app.test_request_context():
    # 生成 URL
    print(url_for('login'))                    # /login
    print(url_for('profile', username='john')) # /profile/john
    print(url_for('profile', username='john', next='/dashboard'))
    # /profile/john?next=%2Fdashboard

4. 请求与响应

4.1 请求对象

from flask import Flask, request

app = Flask(__name__)

@app.route('/request-info', methods=['GET', 'POST'])
def request_info():
    """展示请求信息"""
    info = {
        'method': request.method,
        'url': request.url,
        'path': request.path,
        'remote_addr': request.remote_addr,
        'headers': dict(request.headers),
    }
    
    # GET 参数
    if request.args:
        info['query_params'] = dict(request.args)
    
    # POST 数据
    if request.form:
        info['form_data'] = dict(request.form)
    
    # JSON 数据
    if request.is_json:
        info['json_data'] = request.get_json()
    
    # 文件上传
    if request.files:
        info['files'] = list(request.files.keys())
    
    # Cookies
    info['cookies'] = request.cookies
    
    return info

# 文件上传示例
@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '没有文件', 400
    
    file = request.files['file']
    if file.filename == '':
        return '未选择文件', 400
    
    # 保存文件
    file.save(f'uploads/{file.filename}')
    return f'文件 {file.filename} 上传成功'

4.2 响应构造

from flask import Flask, jsonify, make_response, redirect, abort

app = Flask(__name__)

# 字符串响应
@app.route('/text')
def text_response():
    return '纯文本响应'

# JSON 响应
@app.route('/api/user')
def json_response():
    return jsonify({
        'id': 1,
        'name': '张三',
        'email': 'zhangsan@example.com'
    })

# 自定义状态码和头部
@app.route('/custom')
def custom_response():
    response = make_response('自定义响应', 201)
    response.headers['X-Custom-Header'] = 'MyValue'
    response.set_cookie('session', 'abc123')
    return response

# 重定向
@app.route('/old-page')
def old_page():
    return redirect('/new-page', code=301)

# 错误处理
@app.route('/user/<int:user_id>')
def get_user(user_id):
    if user_id < 1:
        abort(400, '无效的用户 ID')
    
    users = {1: '张三', 2: '李四'}
    if user_id not in users:
        abort(404, '用户不存在')
    
    return {'id': user_id, 'name': users[user_id]}

# 自定义错误页面
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': '页面不存在'}), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({'error': '服务器内部错误'}), 500

5. 模板引擎(Jinja2)

5.1 基本使用

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
    return render_template('hello.html', name=name, items=['A', 'B', 'C'])
<!-- templates/hello.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Hello</title>
</head>
<body>
    <h1>Hello, {{ name|title }}!</h1>
    
    <!-- 条件判断 -->
    {% if name == 'admin' %}
        <p>欢迎管理员</p>
    {% else %}
        <p>欢迎访客</p>
    {% endif %}
    
    <!-- 循环 -->
    <ul>
    {% for item in items %}
        <li>{{ loop.index }}. {{ item }}</li>
    {% else %}
        <li>列表为空</li>
    {% endfor %}
    </ul>
    
    <!-- 过滤器 -->
    <p>{{ name|upper }}</p>
    <p>{{ "hello world"|title }}</p>
    <p>{{ 1234567|filesizeformat }}</p>
</body>
</html>

5.2 模板继承

<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Flask App{% endblock %}</title>
    {% block head %}{% endblock %}
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">首页</a>
        <a href="{{ url_for('about') }}">关于</a>
    </nav>
    
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul class="flashes">
            {% for message in messages %}
                <li>{{ message }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
    
    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>

<!-- templates/index.html -->
{% extends 'base.html' %}

{% block title %}首页 - Flask App{% endblock %}

{% block content %}
    <h1>欢迎来到 Flask</h1>
    <p>这是一个模板继承示例</p>
{% endblock %}

6. 会话与消息闪现

6.1 Session(客户端会话)

from flask import Flask, session, request, redirect, url_for

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # 用于加密 session

@app.route('/login', methods=['POST'])
def do_login():
    username = request.form['username']
    # 验证用户...
    session['username'] = username
    session['logged_in'] = True
    return redirect(url_for('dashboard'))

@app.route('/dashboard')
def dashboard():
    if 'username' not in session:
        return redirect(url_for('login'))
    return f'欢迎, {session["username"]}!'

@app.route('/logout')
def logout():
    session.pop('username', None)
    session.pop('logged_in', None)
    return redirect(url_for('index'))

6.2 消息闪现(Flash Messages)

from flask import Flask, flash, redirect, render_template

app = Flask(__name__)
app.secret_key = 'secret-key'

@app.route('/create', methods=['POST'])
def create_item():
    # 创建逻辑...
    flash('项目创建成功!', 'success')
    return redirect(url_for('index'))

@app.route('/delete/<int:id>')
def delete_item(id):
    # 删除逻辑...
    flash('项目已删除', 'warning')
    return redirect(url_for('index'))

7. 数据库集成(SQLAlchemy)

7.1 配置与模型

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.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(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    
    def __repr__(self):
        return f'<User {self.username}>'

class Post(db.Model):
    """文章模型"""
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    author = db.relationship('User', backref=db.backref('posts', lazy=True))
    created_at = db.Column(db.DateTime, default=db.func.current_timestamp())

# 创建表
with app.app_context():
    db.create_all()

7.2 CRUD 操作

from flask import Flask, request, jsonify

# 创建
@app.route('/users', methods=['POST'])
def create_user():
    data = request.get_json()
    user = User(username=data['username'], email=data['email'])
    db.session.add(user)
    db.session.commit()
    return jsonify({'id': user.id}), 201

# 查询
@app.route('/users')
def get_users():
    users = User.query.all()
    return jsonify([{
        'id': u.id, 
        'username': u.username, 
        'email': u.email
    } for u in users])

@app.route('/users/<int:user_id>')
def get_user(user_id):
    user = User.query.get_or_404(user_id)
    return jsonify({
        'id': user.id,
        'username': user.username,
        'email': user.email,
        'posts': [{'title': p.title} for p in user.posts]
    })

# 更新
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = User.query.get_or_404(user_id)
    data = request.get_json()
    user.username = data.get('username', user.username)
    user.email = data.get('email', user.email)
    db.session.commit()
    return jsonify({'message': '更新成功'})

# 删除
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    user = User.query.get_or_404(user_id)
    db.session.delete(user)
    db.session.commit()
    return jsonify({'message': '删除成功'})

8. 蓝图(Blueprint)

蓝图用于组织大型应用的模块化路由。

# blog.py - 博客蓝图
from flask import Blueprint, render_template

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

@blog_bp.route('/')
def index():
    return render_template('blog/index.html')

@blog_bp.route('/post/<int:post_id>')
def show_post(post_id):
    return f'博客文章 {post_id}'

# auth.py - 认证蓝图
from flask import Blueprint

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

@auth_bp.route('/login')
def login():
    return '登录页'

@auth_bp.route('/register')
def register():
    return '注册页'

# app.py - 注册蓝图
from flask import Flask
from blog import blog_bp
from auth import auth_bp

app = Flask(__name__)
app.register_blueprint(blog_bp)
app.register_blueprint(auth_bp)

# 访问:
# /blog/         → 博客首页
# /blog/post/1   → 博客文章
# /auth/login    → 登录页

9. 应用工厂模式

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app(config_name='development'):
    """应用工厂"""
    app = Flask(__name__)
    
    # 加载配置
    if config_name == 'development':
        app.config.from_mapping(
            SECRET_KEY='dev',
            SQLALCHEMY_DATABASE_URI='sqlite:///dev.db',
            SQLALCHEMY_TRACK_MODIFICATIONS=False
        )
    elif config_name == 'production':
        app.config.from_pyfile('config.py')
    
    # 初始化扩展
    db.init_app(app)
    
    # 注册蓝图
    from . import blog, auth
    app.register_blueprint(blog.bp)
    app.register_blueprint(auth.bp)
    
    # 注册错误处理
    app.register_error_handler(404, lambda e: ('Not Found', 404))
    
    return app

# 启动
# from app import create_app
# app = create_app('development')

10. 小结

概念 Flask 实现 说明
路由 @app.route() 装饰器注册
请求 request 对象 全局上下文
响应 return / jsonify() 自动推断或显式构造
模板 Jinja2 继承 Django 模板风格
会话 session 客户端 Cookie 存储
数据库 Flask-SQLAlchemy 扩展集成
模块化 Blueprint 应用拆分

11. 练习题

  1. 使用 Flask 创建一个 RESTful API,实现图书的增删改查(使用内存存储或 SQLite)。
  2. 为上述 API 添加请求参数验证和错误处理。
  3. 使用蓝图将应用拆分为 apiweb 两个模块。
  4. 对比 Flask 和 Django 的相同功能实现,体会两者的设计差异。

下节预告:我们将学习 FastAPI,这个现代异步框架,体验自动文档生成和类型提示的强大功能。