一篇文章学习Flask
一、Flask基本概念
什么是Flask?
Flask是一个轻量级的Python Web框架,诞生于2010年,由Armin Ronacher开发。它的核心思想是保持简单但易于扩展。
Flask和其他web框架对比
以下是Flask、FastAPI和Django的对比表格:
| 特性 | Flask | FastAPI | Django |
|---|---|---|---|
| 类型 | 微框架(轻量级 Web 框架) | 高性能异步 Web 框架 | 全栈框架(重量级 Web 框架) |
| 复杂度 | 简单轻量 | 适中 | 功能全面但较重 |
| 灵活性 | 非常高(组件自由组合) | 高(更现代的架构) | 较低,遵循"开箱即用"理念 |
| 学习曲线 | 平缓(适合初学者) | 较平缓(需理解类型提示和异步) | 陡峭(需理解Django ORM等概念) |
| 使用场景 | 小型项目、原型开发、REST API | 现代Web API、高性能API、微服务 | 大型网站、CMS、电商、企业应用 |
| 内置功能 | 极少(需自己选择组件) | 少(依赖Pydantic、Starlette等) | 丰富(ORM、Admin、Auth、Forms等) |
| 扩展性 | 高(大量第三方插件) | 高(支持依赖注入和异步扩展) | 中(官方插件多,第三方生态丰富) |
| 性能 | 中 | 高(基于Starlette + async) | 中(同步模型,适合标准Web请求) |
| 文档 | 较好 | 非常好(自动生成OpenAPI文档) | 完善(文档详细全面) |
| 社区支持 | 成熟,社区活跃 | 新兴但快速成长 | 非常成熟,生态完整 |
Flask核心组件
以下是Flask的核心组件:
- Werkzeug:WSGI工具集,处理HTTP请求和响应
- Jinja2:模板引擎,负责渲染HTML页面
- 路由系统:将URL映射到Python函数
- 请求上下文:管理请求期间的数据
二、Flask安装和启动
Flask安装
使用pip命令工具安装:
pip install flask>=3.1.0
第一个Flask应用
创建一个app.py文件,内容如下:
from flask import Flask # 导入Flask类包
# 创建Flask应用实例
app = Flask(__name__)
# 定义视图函数
def hello_flask():
return 'Hello Flask!'
if __name__ == '__main__':
# 定义路由和调用函数
app.route('/')(hello_flask)
app.run() # 启动服务
运行输出日志,默认启动本地的5000端口,访问页面会出现"Hello Flask!"字样。
# 日志如下:
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
定义路由也可以用路由装饰器,将URL映射到视图函数,写法如下:
@app.route('/')
def hello_flask():
return 'Hello Flask!'
开启debug
当我们方法有错误的时候,我们访问路由页面只会提示服务器内部错误,没有提示具体信息,如下图所示:

这个时候我们可以开启debug模式会将错误信息打印在页面上,这样对项目进行调试会非常方便,代码如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def a():
nums = [1, 2, 3]
# 故意写错代码逻辑
print(nums[3])
return 'num = {}'.format(nums[3])
if __name__ == '__main__':
app.run(debug=True) # 开启debug
开启了之后,效果如下图所示:

注意:快捷方式创建flask项目会导致配置debug=True无效,所以最好像创建普通Python项目一样在空目录下创建。
开启debug的四种方法
第一种:
app = Flask(__name__)
app.run(debug=True)
第二种:
app = Flask(__name__)
app.debug = True
第三种:
app = Flask(__name__)
app.config.update(DEBUG=True)
第四种:
创建config.py文件配置debug模式,再在app.py引入配置,如下所示:
# config.py
DEBUG=True
# app.py
import config
app.config.from_object(config)
三、Flask配置系统
初始化参数
创建Flask应用时可以指定多个参数:
app = Flask(
__name__,
static_url_path='/static', # 静态文件访问路径
static_folder='static', # 静态文件存放目录
template_folder='templates' # 模板文件目录
)
配置了static路径之后可以在static目录下放置静态文件,比如:.png、.html等。然后可以通过/static/<name>路由方式访问存放的静态文件。
我存放两个HTML文件然后进行访问测试:
$ tree static
static
├── error.html
└── weihu.html
1 directory, 2 files
我们可以在浏览器访问对应路径对HTML页面进行访问,如下图所示:


应用配置
Flask配置存储在app.config字典中,可以支持多种加载方式:
1.从配置对象进行加载
class Config:
SECRET_KEY = 'your-secret-key'
DEBUG = True
app = Flask(__name__)
app.config.from_object(Config)
2.从Python文件中加载
# config.py
SECRET_KEY = 'your-secret-key'
DEBUG = True
# app.py
app = Flask(__name__)
app.config.from_pyfile('config.py')
3.从环境变量中加载
先创建环境变量设置config文件位置。
export APP_CONFIG="/path/to/config.py"
Python文件去从环境变量中加载配置。
app = Flask(__name__)
app.config.from_envvar('APP_CONFIG')
常用配置项:
| 配置项 | 默认值 | |
|---|---|---|
| DEBUG | 调试模式 | False |
| SECRET_KEY | 加密密钥 | None |
| TESTING | 测试模式 | False |
| SQLALCHEMY_DATABASE_URI | 数据库连接URI | None |
| JSON_AS_ASCII | JSON响应使用ASCII | True |
四、路由与视图
基本路由(静态)
我们设立两个路由,访问/根路径页面会返回"首页",访问/about页面返回"关于我们"。
代码示例:
@app.route('/')
def index():
return '首页'
@app.route('/about')
def about():
return '关于我们'
动态路由(参数)
Flask支持的类型转换器:
- string:默认,接受不带斜线的文本
- int:接受正整形
- float:接受浮点型
- path:类似string但带斜杠路径
- uuid:接受UUID字符串‘
代码示例:
@app.route('/select/<user_name>')
def select_user(user_name):
return "选手 {} 的信息。".format(user_name)
@app.route('/select/<int:user_id>')
def select_info(user_id):
return "第 {} 号选手的信息。".format(user_id)
我们访问进行测试,后面路径跟名字类字符串和跟数字返回的信息不同:
$ curl -l http://127.0.0.1:5000/select/alice
选手 alice 的信息。
$ curl -l http://127.0.0.1:5000/select/256
第 256 号选手的信息。
HTTP方法
HTTP方法类型有:
- GET:获取资源
- POST:创建资源
- PUT:更新资源(全部)
- PATCH:更新资源(部分)
- DELETE:删除资源
- OPTIONS:获取支持的通信选项
- HEAD:获取请求头
代码示例:
# get请求方法
@app.route('/login', methods=['GET'])
def login():
return "登录"
# get和post多个请求方法
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return "正在登录..."
else:
return "登录信息查询中..."
URL生成
使用url_for()方法生成URL:
from flask import url_for
@app.route('/')
def index():
return url_for('select_user', user_name='tom')
@app.route('/select/<user_name>')
def select_user(user_name):
return "选手 {} 的信息。".format(user_name)
上面的index()方法会返回路径"/select/tom",url_for()方法需要传入参数:视图函数,路径值,这里视图函数的路径是"/select/<user_name>",传入的路径是tom,所以返回的路径就是"/select/tom"。
五、请求和响应
请求对象
我们可以通过flask的request模块中获取这些请求对象。
Flask 中常用的请求对象属性清单:
| 属性 / 方法 | 类型 | |
|---|---|---|
| request.method | str | 请求方法,如 ‘GET’, ‘POST’, ‘PUT’, ‘DELETE’ 等 |
| request.url | str | 完整的请求 URL(含协议、主机、路径、查询参数) |
| request.base_url | str | 基础 URL,不包含查询参数 |
| request.path | str | 请求路径部分,例如 /login |
| request.full_path | str | 路径 + 查询字符串,例如 /login?page=2 |
| request.query_string | bytes | 原始查询字符串(字节串) |
| request.args | ImmutableMultiDict | 查询参数(URL 中 ?key=value) |
| request.form | ImmutableMultiDict | 表单字段(POST 提交的 form 表单) |
| request.values | 合并后的参数 | args + form 合并后的结果 |
| request.json | dict or None | JSON 请求体(仅当 Content-Type: application/json) |
| request.get_json() | 方法 | 更灵活地获取 JSON 数据,可加 silent=True |
| request.data | bytes | 原始请求体(未解析的) |
| request.files | ImmutableMultiDict | 上传的文件(FileStorage 对象) |
| request.headers | EnvironHeaders | 所有请求头,像字典一样访问 |
| request.cookies | dict | 客户端发送的 cookies |
| request.remote_addr | str | 客户端 IP 地址 |
| request.host | str | 请求主机(包含端口) |
| request.host_url | str | URL 的主机部分,例如 http://localhost:5000/ |
| request.user_agent | UserAgent | 客户端的 User-Agent 信息 |
| request.environ | dict | WSGI 原始环境字典 |
| request.content_type | str | 请求的 Content-Type,如 application/json |
| request.mimetype | str | MIME 类型,例如 application/json |
| request.is_json | bool | 判断是否是 JSON 请求 |
| request.endpoint | str | 当前匹配的视图函数名 |
| request.view_args | dict | 路由中提取的参数(如 /user/) |
| request.blueprint | str or None | 当前视图所属蓝图(Blueprint)名 |
| request.scheme | str | 请求协议(http 或 https) |
| request.referrer | str or None | 上一页面的 URL |
模拟获取请求对象实例
我们可以模拟登录写一个POST请求的接口,来获取请求对象。代码如下:
from flask import request
@app.route('/login', methods=['POST'])
def user_login():
# 获取传入的三个参数值
username = request.form['username']
password = request.form['password']
remember = request.form.get('remember', False)
# 获取查询参数(?key=value)
page = request.args.get('page', 1, type=int)
# 获取请求头
user_agent = request.headers.get('User-Agent')
# 以字典形式返回这些请求对象的值
return {'login_status:': f'{username} login succeeded.',
'username': username, 'password': password,
'remember': remember, 'page': page, 'header': user_agent}
我们使用请求工具对/login接口发起POST请求,返回的值如下图所示:

我们可以查看实际请求的信息,下图所示:

没有工具或者可以直接用curl命令进行请求,如下:
$ curl -X POST http://127.0.0.1:5000/login\?page\=3 \
-H "User-Agent: CustomClient/1.0" \
-d "username=felix" \
-d "password=123456" \
-d "remember=yes"
# 返回结果如下
{"header":"CustomClient/1.0","login_status:":"felix login succeeded.","page":3,"password":"123456","remember":"yes","username":"felix"}
模拟上传文件
下面例子模拟文件上传的请求,代码如下:
@app.route('https://images.ihavecats.cn/ihavecats', methods=['POST'])
def upload_file():
import os
upload_path = 'uploads'
os.makedirs(upload_path, exist_ok=True)
file = request.files.get('file')
# 判断上传文件参数
if not file:
return jsonify({'error': '未上传文件'}), 400
# 保存文件
save_path = os.path.join(upload_path, file.filename)
file.save(save_path)
return jsonify({'message': 'File uploaded successfully.',
'filename': file.filename, '保存路径': save_path})
我们使用工具进行上传请求,如下图所示:

或者使用curl命令调用,如下:
echo 1111111 > test.txt
curl -X POST http://127.0.0.1:5000https://images.ihavecats.cn/ihavecats \
-F 'file=@"test.txt"'
我们查看该目录文件确实存在该文件,说明上传成功:
$ tree uploads
uploads
└── test.txt
1 directory, 1 file
$ cat uploads/test.txt
1111111
响应处理
Flask视图可以返回多种类型的响应。
字符串返回
就是单纯地返回一个字符串内容。代码如下:
@app.route('/back_string')
def back_string():
return 'Hello Python!'
JSON响应
返回一个标准的JSON数据。代码如下:
@app.route('/back_json')
def back_json():
return jsonify({'name': 'Jack', 'age': 18, 'email': 'example@email.com'})
模板渲染
使用模板文件进行返回页面。代码如下:
app = Flask(
__name__,
static_url_path='/static', # 静态文件访问路径
static_folder='static', # 静态文件存放目录
template_folder='templates' # 模板文件目录
)
@app.route('/model/<name>')
def model(name=None):
return render_template('love.html', name=name)
上面我们初始化对象参数的时候设置的模板存放目录为templates,所以我们写好一个HTML页面放在该目录下,如下所示:
$ tree templates
templates
└── love.html
1 directory, 1 file
我们在浏览页面访问/model/love路径就能看到渲染模板的内容,如下图所示:

重定向
就是访问该路由可以重定向到新的路由上面去。代码如下:
from flask import url_for, redirect
@app.route('/redirect')
def back_redirect():
return redirect(url_for('back_string'))
自定义响应
使用make_response()自定义响应头返回会更加灵活。代码如下:
@app.route('/response')
def make_custom_resp():
# 定义返回类型和数据
response = make_response({'status_code': 200}, 200)
# 定义返回Header
response.headers['X-Powered-By'] = 'Flask'
# 设置Cookie
response.set_cookie('user_id', '38473875')
return response
我们使用curl来调用接口(更加直观方便看信息),如下所示:
$ curl -v http://127.0.0.1:5000/response
* Trying 127.0.0.1:5000...
* Connected to 127.0.0.1 (127.0.0.1) port 5000
> GET /response HTTP/1.1
> Host: 127.0.0.1:5000
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: Werkzeug/3.1.3 Python/3.10.11
< Date: Wed, 30 Jul 2025 08:02:56 GMT
< Content-Type: application/json
< Content-Length: 20
< X-Powered-By: Flask
< Set-Cookie: user_id=38473875; Path=/
< Connection: close
<
{"status_code":200}
* Closing connection
我们可以看到我们设置的响应参数,如下:
# 返回Header:X-Powered-By
X-Powered-By: Flask
# 返回Cookie
Set-Cookie: user_id=38473875; Path=/
# 返回内容
{"status_code":200}
六、蓝图(Blueprint)
蓝图(Blueprint) 是一种组织大型应用的方式,它让你可以将应用拆分成模块化组件,每个组件拥有自己的视图函数、静态资源、模板等。一句话就是蓝图(Blueprint)是Flask项目中用于模块化管理代码的工具。
初始化Blueprint
Blueprint基本语法:
# api.py
from flask import Blueprint # 导入蓝图方法
# 创建蓝图对象,第一个参数为模块名字
bp = Blueprint('api', __name__)
# 蓝图的路由管理
@bp.route('/')
def home_api():
return '欢迎访问api接口!'
加入蓝图对象,代码如下:
# app.py
from flask import Flask
import api # 导入api.py模块
app = Flask(__name__)
# 加入蓝图传入api.py的bp对象,设置初始路由
app.register_blueprint(api.bp, url_prefix='/api')
补充:其实蓝图(Blueprint)也能设置静态资源和模板文件
- 静态路径:
Blueprint('bp', __name__, static_url_path='/static') - 静态文件:
Blueprint('bp', __name__, static_folder='static') - 模板:
Blueprint('bp', __name__, template_folder='templates')
Blueprint项目示例
项目目录结构:
$ tree demo
demo
├── app.py
├── blueprints
│ ├── admin.py
│ └── user.py
├── static
└── templates
4 directories, 3 files
在上面的结构中app.py是主程序文件,blueprints/admin.py和blueprints/user.py文件分别是admin模块和user模块的代码文件,static和templates是静态资源目录和模板目录。
blueprints/admin.py文件,代码如下:
from flask import Blueprint
bp = Blueprint('admin', __name__)
@bp.route('/')
def admin():
return '欢迎访问后台数据!'
@bp.route('/dashboard')
def admin_dash():
return '欢迎访问admin后台页面!'
blueprints/admin.py文件,代码如下:
from flask import Blueprint
bp = Blueprint('user', __name__)
@bp.route('/')
def user():
return '欢迎访问user用户功能!'
@bp.route('/login')
def login():
return '欢迎访问登录页面!'
app.py文件,代码如下:
from flask import Flask
from blueprints import admin, user
class APP:
def __init__(self):
self.app = Flask(
__name__,
static_url_path='/static', # 静态文件访问路径
static_folder='static', # 静态文件存放目录
template_folder='templates' # 模板文件目录
)
self.app.register_blueprint(admin.bp, url_prefix='/admin')
self.app.register_blueprint(user.bp, url_prefix='/user')
self.run()
def run(self):
self.app.run()
if __name__ == '__main__':
APP()
按照路由设计,就是/admin路径访问admin模块功能,/user路径访问user模块功能。
七、模板引擎Jinja2
过滤器
Jinja2提供了多种过滤器处理变量:
- safe:禁用 HTML 转义
- capitalize:首字母大写
- lower:转为小写
- upper:转为大写
- title:每个单词首字母大写
- trim:去掉首尾空格
- striptags:去除 HTML 标签
- length:获取长度
如下所示:
<p>{{ name|upper }}</p> <!-- 转为大写 -->
<p>{{ name|title }}</p> <!-- 每个单词首字母大写 -->
<p>{{ list|join(', ') }}</p> <!-- 列表连接为字符串 -->
<p>{{ value|default('N/A') }}</p> <!-- 默认值 -->
流程控制结构
条件判断
判断传入的值符合的条件。示例如下:
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
循环语句
循环传入的列表值。示例如下:
{% for comment in comments %}
<p>{{ comment }}</p>
{% endfor %}
宏
类似与编程语言中的方法,达到可以反复调用,简化代码和流程的作用。示例如下:
<!-- 定义宏 -->
{% macro input_func(name, type='text', value='') %}
<input name="{{ name }}" type="{{ type }}" value="{{ value }}">
{% endmacro %}
<!-- 使用宏 -->
<p>用户:{{ input_func('username') }}</p>
<p>密码:{{ input_func('password', type='password') }}</p>
模板继承
主模板文件base/base.html,定义模板,代码如下:
<!DOCTYPE html>
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Website</title>
{% endblock %}
</head>
<body>
<h1>主要内容:</h1>
{% block content %}{% endblock %}
</body>
</html>
子模板文件subcontent.html引用主模板文件base/base.html,将内容代入到主模板文件中进行展示,代码如下:
{% extends "base/base.html" %}
{% block title %}Jack{% endblock %}
{% block content %}
<h3>静夜思</h3>
{% for content in contents %}
<p>{{ content }}</p>
{% endfor %}
{% endblock %}
我们在Python代码中引用子模板,查看渲染效果,代码如下:
@app.route('/load_web')
def load_web():
contents = [
"床前明月光,",
"疑是地上霜。",
"举头望明月,",
"低头思故乡。"
]
data = render_template('subcontent.html', contents=contents)
return data
访问页面,效果如下图所示:

一个简单模板示例
我们用上面的引擎语法编写一个模板来作为示例。
模板test.html内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello from Flask</title>
</head>
<body>
<!-- 使用条件判断 -->
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
<h3>静夜思</h3>
<!-- 使用循环结构 -->
{% for comment in comments %}
<p>{{ comment }}</p>
{% endfor %}
<h3>Python流程控制:</h3>
<ul>
{% for content in contents %}
<!-- 使用title过滤器 -->
<li>{{ content |title }}</li>
{% endfor %}
</ul>
<h3>物品列表:</h3>
<!-- 使用join过滤器 -->
<p>{{ things |join(', ') }}</p>
<h3>输入框:</h3>
<!-- 定义宏 -->
{% macro input_func(name, type='text', value='') %}
<input name="{{ name }}" type="{{ type }}" value="{{ value }}">
{% endmacro %}
<!-- 使用宏 -->
<p>用户:{{ input_func('username') }}</p>
<p>密码:{{ input_func('password', type='password') }}</p>
</body>
</html>
Python代码如下:
@app.route('/read_template')
def read_template():
comments = [
"床前明月光,",
"疑是地上霜。",
"举头望明月,",
"低头思故乡。"
]
contents = ['first.条件语句:if、elif、else',
'second.循环结构:for、while',
'third.控制语句:break、continue、pass']
things = ['apple', 'banana', 'orange']
data = render_template('test.html', name='Jack',
comments=comments, contents=contents, things=things)
return data
访问页面效果如下图所示:

八、Flask高级特性
请求钩子
Flask提供了四种请求钩子,如下所示:
# 在第一个请求之前运行
@app.before_first_request
def before_first_request():
pass
# 在每个请求之前运行
@app.before_request
def before_request():
pass
# 如果没有未处理的异常,在每个请求之后运行
@app.after_request
def after_request(response):
return response
# 在每个请求之后运行,即使有未处理的异常
@app.teardown_request
def teardown_request(exception):
pass
错误处理
根据响应码给出错误响应。
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500