一篇文章学习Flask

TOC

一、Flask基本概念

什么是Flask?

Flask是一个轻量级的Python Web框架,诞生于2010年,由Armin Ronacher开发。它的核心思想是保持简单但易于扩展。

Flask和其他web框架对比

以下是FlaskFastAPIDjango的对比表格:

特性 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

当我们方法有错误的时候,我们访问路由页面只会提示服务器内部错误,没有提示具体信息,如下图所示:
flask
这个时候我们可以开启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
注意:快捷方式创建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
flask

应用配置

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请求,返回的值如下图所示:
flask
我们可以查看实际请求的信息,下图所示:
flask
没有工具或者可以直接用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})

我们使用工具进行上传请求,如下图所示:
flask
或者使用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路径就能看到渲染模板的内容,如下图所示:
flask

重定向

就是访问该路由可以重定向到新的路由上面去。代码如下:

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.pyblueprints/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

访问页面,效果如下图所示:
flask

一个简单模板示例

我们用上面的引擎语法编写一个模板来作为示例。
模板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高级特性

请求钩子

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