Flask 基础入门
2026/3/20大约 12 分钟
Flask 基础入门
第一章:Flask 框架概述
什么是 Flask?
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它由 Armin Ronacher 于 2010 年创建,基于 Werkzeug WSGI 工具包和 Jinja2 模板引擎。Flask 被称为"微框架"(Microframework),因为它保持核心简单但可扩展。
设计理念
Flask 的设计哲学是:
- 保持核心简单:Flask 不会为你做太多决定,它提供最基础的功能,让你自由选择组件
- 可扩展性强:通过扩展(Extensions)机制,可以轻松添加各种功能
- 显式优于隐式:Flask 不会自动做"魔法"操作,一切都是明确可见的
- 文档完善:Flask 拥有优秀的官方文档
Flask 核心组件
Flask 核心架构
├── Werkzeug(WSGI 工具包)
│ ├── 请求和响应对象
│ ├── URL 路由
│ ├── HTTP 工具函数
│ └── 开发服务器和调试器
├── Jinja2(模板引擎)
│ ├── 模板继承
│ ├── 自动 HTML 转义
│ ├── 沙箱执行环境
│ └── 自定义过滤器和标签
└── Flask 核心
├── 应用配置
├── 蓝图系统
├── 扩展加载
└── 信号机制
Flask vs Django
| 特性 | Flask | Django |
|---|---|---|
| 设计理念 | 微框架,按需添加 | 全功能框架,开箱即用 |
| 学习曲线 | 平缓 | 较陡 |
| 灵活性 | 极高 | 中等 |
| 内置功能 | 最小化 | 丰富(ORM、Admin 等) |
| 数据库支持 | 需扩展 | 内置 ORM |
| 项目规模 | 小到中型 | 中到大型 |
| 开发速度 | 快(小项目) | 快(大项目) |
| 定制程度 | 完全自由 | 遵循约定 |
适用场景
Flask 最适合以下场景:
- RESTful API 服务:构建 JSON API 后端
- 微服务架构:作为微服务组件
- 原型开发:快速验证想法
- 小到中型 Web 应用:博客、CMS 等
- 需要高度定制的项目:对技术栈有特定要求
第二章:环境搭建
Python 版本要求
Flask 2.x 需要 Python 3.8+,推荐使用 Python 3.10 或更高版本。
# 检查 Python 版本
python --version
# Python 3.10.x
虚拟环境配置
# 创建项目目录
mkdir flask-project
cd flask-project
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows
venv\Scripts\activate
# Linux/macOS
source venv/bin/activate
# 确认虚拟环境已激活
which python # 应该指向 venv 目录
安装 Flask
# 安装最新版 Flask
pip install Flask
# 安装指定版本
pip install Flask==3.0.0
# 查看已安装的包
pip list
# 生成 requirements.txt
pip freeze > requirements.txt
使用 Poetry 管理依赖(推荐)
# 安装 Poetry
pip install poetry
# 初始化项目
poetry init
# 添加 Flask 依赖
poetry add flask
# 安装开发依赖
poetry add --group dev pytest flask-testing
# 激活虚拟环境
poetry shell
项目结构(入门级)
flask-project/
├── venv/ # 虚拟环境
├── app.py # 主应用文件
├── requirements.txt # 依赖列表
└── README.md
第三章:第一个 Flask 应用
Hello World
创建 app.py:
from flask import Flask
# 创建 Flask 应用实例
# __name__ 用于确定应用的根路径
app = Flask(__name__)
# 定义路由和视图函数
@app.route('/')
def hello_world():
return 'Hello, World!'
# 运行应用
if __name__ == '__main__':
app.run(debug=True)
运行应用
# 方式一:直接运行
python app.py
# 方式二:使用 flask 命令(推荐)
# 设置环境变量
export FLASK_APP=app.py # Linux/macOS
set FLASK_APP=app.py # Windows CMD
$env:FLASK_APP="app.py" # Windows PowerShell
# 运行开发服务器
flask run
# 指定主机和端口
flask run --host=0.0.0.0 --port=5000
# 开启调试模式
flask run --debug
调试模式详解
调试模式提供两个关键功能:
- 自动重载:代码修改后自动重启服务器
- 交互式调试器:发生错误时在浏览器中调试
# 方式一:在代码中开启
app.run(debug=True)
# 方式二:环境变量
export FLASK_DEBUG=1
# 方式三:配置对象
app.config['DEBUG'] = True
安全警告
永远不要在生产环境中开启调试模式!调试器允许执行任意 Python 代码。
应用实例详解
from flask import Flask
# Flask 构造函数参数
app = Flask(
__name__, # 导入名称,用于定位资源
static_url_path='/static', # 静态文件 URL 前缀
static_folder='static', # 静态文件目录
template_folder='templates', # 模板目录
instance_path=None, # 实例文件夹路径
instance_relative_config=False, # 配置文件是否相对于实例路径
root_path=None # 应用根路径
)
# 查看应用信息
print(app.name) # 应用名称
print(app.root_path) # 根目录路径
print(app.static_folder) # 静态文件目录
print(app.template_folder) # 模板目录
第四章:应用配置
配置方式
Flask 提供多种配置方式:
from flask import Flask
app = Flask(__name__)
# 方式一:直接设置
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'your-secret-key'
# 方式二:使用 update 批量设置
app.config.update(
DEBUG=True,
SECRET_KEY='your-secret-key',
DATABASE_URI='sqlite:///app.db'
)
# 方式三:从 Python 文件加载
app.config.from_pyfile('config.py')
# 方式四:从对象加载
class Config:
DEBUG = True
SECRET_KEY = 'your-secret-key'
app.config.from_object(Config)
# 方式五:从环境变量加载
app.config.from_envvar('APP_CONFIG_FILE')
# 方式六:从 JSON 文件加载
app.config.from_json('config.json')
内置配置项
# 核心配置
DEBUG = False # 调试模式
TESTING = False # 测试模式
SECRET_KEY = None # 会话密钥
PERMANENT_SESSION_LIFETIME = timedelta(days=31) # 会话过期时间
# 服务器配置
SERVER_NAME = None # 服务器名称
PREFERRED_URL_SCHEME = 'http' # URL 方案
# JSON 配置
JSON_SORT_KEYS = True # JSON 键排序
JSONIFY_PRETTYPRINT_REGULAR = False # 美化 JSON 输出
# 模板配置
TEMPLATES_AUTO_RELOAD = None # 模板自动重载
EXPLAIN_TEMPLATE_LOADING = False # 解释模板加载
# 会话配置
SESSION_COOKIE_NAME = 'session' # Cookie 名称
SESSION_COOKIE_SECURE = False # 仅 HTTPS
SESSION_COOKIE_HTTPONLY = True # 禁止 JS 访问
SESSION_COOKIE_SAMESITE = 'Lax' # SameSite 策略
环境配置分离
创建不同环境的配置类:
# config.py
import os
from datetime import timedelta
class Config:
"""基础配置"""
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string'
SQLALCHEMY_TRACK_MODIFICATIONS = False
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
"""开发环境配置"""
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///dev.db'
class TestingConfig(Config):
"""测试环境配置"""
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///test.db'
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
"""生产环境配置"""
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///prod.db'
@classmethod
def init_app(cls, app):
Config.init_app(app)
# 生产环境特定初始化
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10240,
backupCount=10
)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
# 配置映射
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
使用配置
# app.py
import os
from flask import Flask
from config import config
def create_app(config_name=None):
if config_name is None:
config_name = os.environ.get('FLASK_CONFIG', 'default')
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
# 注册蓝图、扩展等...
return app
第五章:请求与响应
请求对象
Flask 使用 request 对象封装客户端请求:
from flask import Flask, request
app = Flask(__name__)
@app.route('/example', methods=['GET', 'POST'])
def example():
# 请求方法
method = request.method # 'GET', 'POST', etc.
# URL 参数(查询字符串)
page = request.args.get('page', 1, type=int)
# 表单数据
username = request.form.get('username')
password = request.form.get('password')
# JSON 数据
data = request.get_json() # 或 request.json
# 请求头
content_type = request.headers.get('Content-Type')
auth = request.headers.get('Authorization')
# Cookies
token = request.cookies.get('token')
# 文件上传
file = request.files.get('file')
if file:
filename = file.filename
file.save(f'uploads/{filename}')
# 请求 URL 信息
url = request.url # 完整 URL
path = request.path # 路径部分
host = request.host # 主机名
# 客户端信息
ip = request.remote_addr # 客户端 IP
user_agent = request.user_agent.string # User-Agent
return 'OK'
request 对象属性一览
| 属性 | 说明 | 示例 |
|---|---|---|
method | HTTP 方法 | 'GET', 'POST' |
args | URL 查询参数 | request.args.get('id') |
form | 表单数据 | request.form['name'] |
json | JSON 数据 | request.json |
data | 原始请求数据 | request.data |
files | 上传的文件 | request.files['file'] |
headers | 请求头 | request.headers['Host'] |
cookies | Cookie | request.cookies.get('token') |
values | args + form | request.values.get('key') |
path | URL 路径 | '/user/1' |
url | 完整 URL | 'http://...' |
base_url | 不带查询的 URL | 'http://...' |
remote_addr | 客户端 IP | '192.168.1.1' |
响应对象
from flask import Flask, make_response, jsonify, redirect, url_for
app = Flask(__name__)
# 简单字符串响应
@app.route('/text')
def text_response():
return 'Hello World'
# 指定状态码
@app.route('/error')
def error_response():
return 'Not Found', 404
# 指定响应头
@app.route('/headers')
def headers_response():
return 'OK', 200, {'X-Custom-Header': 'value'}
# 使用 make_response
@app.route('/custom')
def custom_response():
response = make_response('Custom Response')
response.status_code = 200
response.headers['X-Custom'] = 'Value'
response.set_cookie('user_id', '123', max_age=3600)
return response
# JSON 响应
@app.route('/json')
def json_response():
return jsonify({
'status': 'success',
'data': {'id': 1, 'name': 'Flask'}
})
# 重定向
@app.route('/redirect')
def redirect_example():
return redirect(url_for('index'))
# 元组响应格式
@app.route('/tuple')
def tuple_response():
# (body, status, headers)
return ('Response Body', 201, {'Location': '/new-resource'})
响应 MIME 类型
from flask import Flask, Response, make_response
app = Flask(__name__)
# 返回 XML
@app.route('/xml')
def xml_response():
xml = '''<?xml version="1.0"?>
<data>
<item>Hello</item>
</data>'''
return Response(xml, mimetype='application/xml')
# 返回 CSV
@app.route('/csv')
def csv_response():
csv = "name,age\nAlice,25\nBob,30"
response = make_response(csv)
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=data.csv'
return response
# 返回图片
@app.route('/image')
def image_response():
with open('image.png', 'rb') as f:
image_data = f.read()
return Response(image_data, mimetype='image/png')
第六章:URL 构建与重定向
url_for 函数
url_for() 用于构建 URL,优点:
- 自动处理特殊字符转义
- 更改路由时自动更新链接
- 统一管理 URL
from flask import Flask, url_for, redirect
app = Flask(__name__)
@app.route('/')
def index():
return 'Index'
@app.route('/user/<username>')
def profile(username):
return f'User: {username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post: {post_id}'
@app.route('/login')
def login():
return 'Login'
# 使用 url_for
with app.test_request_context():
print(url_for('index')) # '/'
print(url_for('profile', username='john')) # '/user/john'
print(url_for('show_post', post_id=1)) # '/post/1'
print(url_for('login')) # '/login'
print(url_for('login', next='/dashboard')) # '/login?next=/dashboard'
print(url_for('static', filename='style.css')) # '/static/style.css'
重定向
from flask import Flask, redirect, url_for, abort
app = Flask(__name__)
@app.route('/admin')
def admin():
# 检查权限
if not is_admin():
return redirect(url_for('login'))
return 'Admin Panel'
@app.route('/old-page')
def old_page():
# 永久重定向 (301)
return redirect(url_for('new_page'), code=301)
@app.route('/new-page')
def new_page():
return 'New Page'
# 使用 abort 中断请求
@app.route('/protected')
def protected():
if not authenticated():
abort(401) # Unauthorized
return 'Protected Content'
第七章:静态文件与模板
静态文件
Flask 自动提供 /static 路由:
flask-project/
├── app.py
└── static/
├── css/
│ └── style.css
├── js/
│ └── app.js
└── images/
└── logo.png
from flask import Flask, url_for
app = Flask(__name__)
# 在模板中引用静态文件
# <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
# <script src="{{ url_for('static', filename='js/app.js') }}"></script>
# <img src="{{ url_for('static', filename='images/logo.png') }}">
# 自定义静态文件配置
app = Flask(__name__,
static_folder='assets', # 静态文件目录
static_url_path='/assets' # URL 前缀
)
模板基础
创建模板目录和文件:
flask-project/
├── app.py
└── templates/
├── base.html
├── index.html
└── user/
└── profile.html
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', title='Home')
@app.route('/user/<username>')
def profile(username):
user = {'name': username, 'bio': 'Python Developer'}
return render_template('user/profile.html', user=user)
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %} - My App</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Home</a>
</nav>
<main>{% block content %}{% endblock %}</main>
<footer>© 2024 My App</footer>
</body>
</html>
<!-- templates/index.html -->
{% extends 'base.html' %} {% block title %}{{ title }}{% endblock %} {% block
content %}
<h1>Welcome to Flask</h1>
<p>This is the home page.</p>
{% endblock %}
第八章:错误处理
自定义错误页面
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_error(error):
return render_template('errors/500.html'), 500
@app.errorhandler(403)
def forbidden(error):
return render_template('errors/403.html'), 403
# JSON API 错误处理
@app.errorhandler(404)
def not_found_json(error):
if request.accept_mimetypes.accept_json:
return jsonify({'error': 'Not found', 'code': 404}), 404
return render_template('errors/404.html'), 404
自定义异常
from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
class APIException(Exception):
"""自定义 API 异常"""
def __init__(self, message, status_code=400, payload=None):
super().__init__()
self.message = message
self.status_code = status_code
self.payload = payload
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
rv['code'] = self.status_code
return rv
@app.errorhandler(APIException)
def handle_api_exception(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
# 使用自定义异常
@app.route('/api/user/<int:user_id>')
def get_user(user_id):
user = find_user(user_id)
if user is None:
raise APIException('User not found', status_code=404)
return jsonify(user)
第九章:日志记录
基础日志
from flask import Flask
import logging
app = Flask(__name__)
# Flask 内置日志
@app.route('/')
def index():
app.logger.debug('Debug message')
app.logger.info('Info message')
app.logger.warning('Warning message')
app.logger.error('Error message')
return 'OK'
# 配置日志
if not app.debug:
# 文件日志
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
高级日志配置
import logging
from logging.handlers import RotatingFileHandler, SMTPHandler
from flask import Flask
app = Flask(__name__)
def configure_logging(app):
# 日志格式
formatter = logging.Formatter(
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
)
# 控制台日志
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter)
# 文件日志(轮转)
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=10
)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
# 邮件通知(仅错误)
if not app.debug:
mail_handler = SMTPHandler(
mailhost=('smtp.example.com', 587),
fromaddr='server@example.com',
toaddrs=['admin@example.com'],
subject='Application Error',
credentials=('username', 'password'),
secure=()
)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
# 添加处理器
app.logger.addHandler(console_handler)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.DEBUG)
# 初始化日志
configure_logging(app)
第十章:上下文
应用上下文
应用上下文存储应用级别的数据:
from flask import Flask, current_app, g
app = Flask(__name__)
with app.app_context():
# 在应用上下文中
print(current_app.name) # 当前应用
# g 对象:存储请求期间的数据
g.user = get_current_user()
# 使用 g 对象
@app.before_request
def before_request():
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
db = g.pop('db', None)
if db is not None:
db.close()
请求上下文
请求上下文包含每个请求的信息:
from flask import Flask, request, session
app = Flask(__name__)
app.secret_key = 'your-secret-key'
@app.route('/example')
def example():
# request 对象 - 请求上下文
user_agent = request.user_agent.string
# session 对象 - 请求上下文
session['visits'] = session.get('visits', 0) + 1
return f'Visits: {session["visits"]}'
# 手动推送请求上下文
with app.test_request_context('/example', method='POST'):
print(request.method) # 'POST'
print(request.path) # '/example'
上下文生命周期
请求到达
│
├── 创建应用上下文
│ └── 推送 current_app, g
│
├── 创建请求上下文
│ └── 推送 request, session
│
├── 执行 before_request 钩子
│
├── 执行视图函数
│
├── 执行 after_request 钩子
│
├── 弹出请求上下文
│ └── 执行 teardown_request
│
└── 弹出应用上下文
└── 执行 teardown_appcontext
第十一章:会话管理
使用 Session
from flask import Flask, session, redirect, url_for, request
app = Flask(__name__)
app.secret_key = 'your-secret-key-here' # 必须设置
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
session['logged_in'] = True
session.permanent = True # 持久会话
return redirect(url_for('dashboard'))
return render_template('login.html')
@app.route('/dashboard')
def dashboard():
if 'username' in session:
return f'Welcome, {session["username"]}'
return redirect(url_for('login'))
@app.route('/logout')
def logout():
session.clear() # 清除所有会话数据
# 或 session.pop('username', None)
return redirect(url_for('index'))
会话配置
from datetime import timedelta
app.config.update(
SECRET_KEY='your-secret-key',
PERMANENT_SESSION_LIFETIME=timedelta(days=7),
SESSION_COOKIE_SECURE=True, # 仅 HTTPS
SESSION_COOKIE_HTTPONLY=True, # 禁止 JS 访问
SESSION_COOKIE_SAMESITE='Lax', # 防 CSRF
SESSION_COOKIE_NAME='my_session' # 自定义名称
)
服务端会话(Flask-Session)
from flask import Flask, session
from flask_session import Session
import redis
app = Flask(__name__)
# 配置 Redis 作为会话存储
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')
app.config['SESSION_PERMANENT'] = True
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24)
Session(app)
@app.route('/set')
def set_session():
session['key'] = 'value'
session['user'] = {'id': 1, 'name': 'Alice'} # 可存储复杂对象
return 'Session set'
第十二章:钩子函数(请求处理生命周期)
请求钩子
from flask import Flask, request, g
import time
app = Flask(__name__)
@app.before_request
def before_request():
"""每个请求前执行"""
g.start_time = time.time()
g.user = get_current_user()
# 返回非 None 值将中断请求
if maintenance_mode():
return 'Site under maintenance', 503
@app.after_request
def after_request(response):
"""每个请求后执行(无异常时)"""
# 计算请求耗时
duration = time.time() - g.start_time
response.headers['X-Request-Duration'] = str(duration)
return response # 必须返回 response
@app.teardown_request
def teardown_request(exception):
"""请求结束时执行(无论是否有异常)"""
if exception:
app.logger.error(f'Error: {exception}')
# 清理资源
db = g.pop('db', None)
if db:
db.close()
@app.teardown_appcontext
def teardown_appcontext(exception):
"""应用上下文结束时执行"""
pass
首次请求钩子
# Flask 2.3+ 已移除 @before_first_request
# 使用以下替代方案
def init_db():
"""初始化数据库"""
pass
with app.app_context():
init_db()
# 或使用工厂函数中初始化
def create_app():
app = Flask(__name__)
with app.app_context():
init_db()
return app
执行顺序示例
@app.before_request
def first_before():
print('1. First before_request')
@app.before_request
def second_before():
print('2. Second before_request')
@app.route('/')
def index():
print('3. View function')
return 'OK'
@app.after_request
def first_after(response):
print('4. First after_request')
return response
@app.after_request
def second_after(response):
print('5. Second after_request')
return response
# 输出顺序:
# 1. First before_request
# 2. Second before_request
# 3. View function
# 5. Second after_request (LIFO)
# 4. First after_request
总结
本章介绍了 Flask 的基础知识:
- 框架概述:Flask 的设计理念和核心组件
- 环境搭建:虚拟环境和依赖管理
- 第一个应用:Hello World 和运行方式
- 应用配置:多种配置方式和环境分离
- 请求与响应:request/response 对象详解
- URL 构建:url_for 和重定向
- 静态文件与模板:资源管理基础
- 错误处理:自定义错误页面和异常
- 日志记录:日志配置和使用
- 上下文:应用上下文和请求上下文
- 会话管理:Session 使用和配置
- 钩子函数:请求生命周期管理
下一章我们将深入学习路由系统和视图函数。