Flask入门学习指南

Flask是一个轻量级的Python Web框架,简单易学且功能强大。


1. Flask简介

Flask是一个微框架,核心简单但可通过扩展增加功能。它使用Werkzeug作为WSGI工具库,Jinja2作为模板引擎。

虚拟环境

使用虚拟环境可以在开发和生产环境下管理项目依赖。

虚拟环境解决了什么问题?你的 Python 项目越多,就越有可能需要使用不同版本的 Python 包,甚至 Python 本身。某个项目使用的新版本的库可能会破坏其他项目的兼容性。

虚拟环境是 Python 库的独立集合,每一个项目对应一个虚拟环境。安装到某个项目的包不会影响其他项目或是操作系统层级的包。

Python 自带的 venv 模块可以用来创建虚拟环境。

创建虚拟环境

创建一个项目文件夹,并在其中创建虚拟环境文件夹 .myvenv

复制代码
> mkdir Flask_Test
> cd Flask_Test
> py -3 -m venv .myvenv

(在桌面上新建文件夹,打开文件夹,点击空白处,通过vs code打开并运行上述代码即可)

激活虚拟环境

在开始动手做你的项目之前,先激活对应的虚拟环境:

复制代码
> .myvenv\Scripts\activate

2. 环境搭建

安装Flask

复制代码
pip install flask

创建第一个Flask应用

复制代码
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

所以这些代码做了什么?

  1. 首先,我们导入了 Flask 类。该类的一个实例将成为我们的 WSGI 应用。

  2. 接下来我们创建这个类的一个实例。第一个参数是程序模块或包的名称,这是必要的,以便 Flask 知道在哪里查找资源,例如模板和静态文件。 __name__ 是一个方便的方式,适用于大多数情况。

  3. 然后我们使用 route() 装饰器来告诉 Flask 哪个 URL 应该触发我们的函数。

  4. 该函数返回我们想要在用户浏览器中显示的内容。默认的内容类型是 HTML,因此字符串中的 HTML 将由浏览器显示。

将这个文件其保存为 hello.py 或类似的名称。不要将程序文件命名为 flask.py,因为这会与 Flask 本身冲突。

要运行该程序,请使用 flaskpython -m flask 命令。您需要使用 --app 选项告诉 Flask 程序的位置。

写完py代码后注意保存

手动打开浏览器,现在转到 http://127.0.0.1:5000/ ,您应该会看到您的 hello world 问候。

外部可见的服务器

如果你运行服务器,你会发现你只能从你自己的电脑访问它,而不能从网络上的任何其他电脑访问。这是默认设置,因为在调试模式下,应用程序的用户可以在你的电脑上执行任意 Python 代码。

如果您禁用了调试器或信任网络上的用户,则只需在命令行中添加 --host=0.0.0.0 即可使服务器公开可用:

复制代码
$ flask run --host=0.0.0.0

这会告诉你的操作系统监听所有公共 IP。

3.调试模式

flask run 命令的功能远不止启动开发服务器。启用调试模式后,服务器会在代码更改时自动重新加载;如果请求过程中出现错误,则会在浏览器中显示交互式调试器。

警告

该调试器允许从浏览器执行任意 Python 代码。虽然它受密码保护,但仍然存在重大安全风险。请勿在生产环境中运行开发用服务器或调试器。

要启用调试模式,请使用 --debug 选项。

复制代码
$ flask --app hello run --debug
 * Serving Flask app 'hello'
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: nnn-nnn-nnn

4.HTML转义

返回 HTML(Flask 中的默认响应类型)时,必须对输出中嵌入的任何用户提供的值进行转义,以防止注入攻击。稍后介绍的使用 Jinja 渲染的 HTML 模板将自动执行此操作。

此处所示的 escape() 可以手动使用。为了简洁起见,大多数示例中都省略了它,但您应始终注意如何处理不受信任的数据。

复制代码
from markupsafe import escape

@app.route("/<name>")
def hello(name):
    return f"Hello, {escape(name)}!"

如果用户设法提交名称 <script>alert("bad")</script> ,则转义会导致其呈现为文本,而不是在用户的浏览器中运行脚本。

路径中的 <name> 从 URL 中捕获一个值并将其传递给视图函数。这些变量规则如下所述。

5.路径

现代 Web 应用程序使用有意义的 URL 来帮助用户。如果页面使用有意义的用户能够记住的URL,那么他们更有可能喜欢该页面并再次访问。

使用 route() 装饰器将函数绑定到 URL:

复制代码
@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

你还可以做更多!你可以使 URL 的某些部分动态化,并将多个规则附加到一个函数。

变量规则

您可以通过使用 <variable_name>``标记部分URL来向其添加变量部分。然后,您的函数将接收"<variable_name>"作为参数。您也可以选择使用转换器来指定参数的类型,例如 ``<converter:variable_name>.

复制代码
from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return f'User {escape(username)}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return f'Post {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return f'Subpath {escape(subpath)}'

转换器类型:

|----------------|----------------------|
| string (字符串) | (默认)接受任何不带斜杠的文本 |
| int (正整数) | 接受正整数 |
| float (浮点数) | 接受正浮点数 |
| path (路径) | 与 string 类似,但也接受斜杠 |
| uuid (唯一标识符) | 接受 UUID 字符串 |

唯一的 URL / 重定向行为

以下两个规则在尾部斜杠的使用上有所不同。:

复制代码
@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

projects 端点的规范 URL 末尾有一个斜杠。它类似于文件系统中的文件夹。如果您访问的 URL 末尾没有斜杠(/projects),Flask 会将您重定向到末尾带有斜杠的规范 URL (/projects/)。

about 端点的规范 URL 没有尾部斜杠。它类似于文件的路径名。访问带有尾部斜杠的 URL("/about/")会产生 404 "Not Found"错误。这有助于保持这些资源的 URL 唯一,从而帮助搜索引擎避免对同一页面进行两次索引。

构建URL

要构建指向特定函数的 URL,请使用 url_for() 函数。它接受函数名称作为其第一个参数,以及任意数量的参数,每个参数对应于 URL 规则的一个变量部分。未知的变量部分将作为查询参数附加到 URL 中。

为什么要使用函数 url_for() 来构建 URL,而不是将它们硬编码到模板中?

  1. 使用函数 url_for() 通常比硬编码 URL 更具描述性 。

  2. 您可以一次性更改 URL,而不需要手动去更改硬编码的 URL。

  3. URL 构建透明地处理特殊字符的转义。

  4. 生成的路径始终是绝对路径,避免了在浏览器中相对路径的意外行为。

  5. 如果您的应用程序被放置在 URL 根目录之外,例如,在 /myapplication 而不是 /url_for() 会为您正确处理。

例如,这里我们使用 test_request_context() 方法来测试 url_for()test_request_context() 会告诉 Flask 即使在使用 Python shell 时也要像在处理请求一样运行。参见 上下文局部变量

复制代码
from flask import url_for

@app.route('/')
def index():
    return 'index'

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

@app.route('/user/<username>')
def profile(username):
    return f'{username}\'s profile'

with app.test_request_context():
    print(url_for('index'))
    print(url_for('login'))
    print(url_for('login', next='/'))
    print(url_for('profile', username='John Doe'))
复制代码
/
/login
/login?next=/
/user/John%20Doe

HTTP 方法

Web 应用程序在访问 URL 时使用不同的 HTTP 方法。在使用 Flask 时,您应该熟悉这些 HTTP 方法。默认情况下,一个路径仅响应 GET 请求。您可以使用 route() 装饰器的 methods 参数来处理不同的 HTTP 方法。:

复制代码
from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

上面的示例将路径的所有HTTP方法保存在一个函数中,如果每个部分都要使用一些公共数据,这将很有用。

您还可以将不同方法的视图分离到不同的函数中。Flask 提供了适用于每种常见的 HTTP 方法的函数 get()post() 等来装饰这些路径的快捷方式。

复制代码
@app.get('/login')
def login_get():
    return show_the_login_form()

@app.post('/login')
def login_post():
    return do_the_login()

如果存在 GET,Flask 会自动添加对 HEAD 方法的支持,并根据 HTTP RFC 处理 HEAD 请求。同样,OPTIONS 也会自动为您实现。

  • GET 方法 :当用户在浏览器输入 http://xxx/login 或者点击 "登录页链接" 时,就是用 GET 方法请求,此时函数会执行 else 分支,返回登录页面。
  • POST 方法 :当用户在登录页面填写完账号密码,点击 "登录按钮" 时,表单会用 POST 方法把数据提交到服务器,此时函数执行 if 分支,处理登录逻辑。这种方式的好处是把登录页面的展示和登录逻辑的处理放在同一个函数里,如果两者需要共享一些数据(比如页面样式配置),会很方便。

Flask 还提供了更直观的 "拆分式" 写法,把不同 HTTP 方法的逻辑拆到不同函数里:

复制代码
from flask import Flask, render_template, request, redirect

app = Flask(__name__)

# 处理 GET 请求:展示登录表单
@app.get('/login')
def show_login_form():
    return render_template('login.html')  # 渲染登录页面模板

# 处理 POST 请求:处理登录逻辑
@app.post('/login')
def do_login():
    username = request.form.get('username')
    password = request.form.get('password')
    # 这里写验证账号密码的逻辑,比如和数据库比对
    return redirect('/home')  # 验证通过后跳转到首页
  • @app.get('/login') 专门处理该路径的 GET 请求,负责展示页面。
  • @app.post('/login') 专门处理该路径的 POST 请求,负责处理提交的数据。这种方式的好处是代码逻辑更清晰,适合复杂场景(比如登录和注册的逻辑完全独立)

简单总结一下:

  • 如果你想让 "展示页面" 和 "处理提交" 的逻辑有一定关联,可以用 methods 参数把它们放在一个函数里。
  • 如果你想让两种逻辑完全分开,更易维护,就用 @app.get@app.post 这样的快捷装饰器。

6.静态文件

动态 Web 应用程序也需要静态文件。CSS 和 JavaScript 文件通常就来自它们。理想情况下,您的 Web 服务器已配置为提供这些文件,但在开发过程中,Flask 也可以做到这一点。只需在您的包中或模块旁边创建一个名为 static 的文件夹,它就可以在应用程序的 /static 目录下找到。

要生成静态文件的 URL,请使用特殊的 'static' 端点名称:

复制代码
url_for('static', filename='style.css')

该文件必须作为 static/style.css 存储在文件系统中。

7.渲染模板

在 Python 中生成 HTML 并不好玩,实际上相当繁琐,因为你必须自己进行 HTML 转义才能保证应用程序的安全。因此,Flask 会自动为你配置 Jinja2 模板引擎。

模板可用于生成任何类型的文本文件。对于 Web 应用程序,您会主要生成 HTML 页面,但您也可以生成 Markdown 文件、电子邮件的文本以及其他任何内容。

有关 HTML、CSS 和其他 Web API 的参考,请使用 MDN Web Docs.

要渲染模板,您可以使用 render_template() 方法。您只需提供模板名称以及要作为关键字参数传递给模板引擎的变量即可。以下是如何渲染模板的简单示例:

复制代码
from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', person=name)

Flask 会在 templates 文件夹中查找模板。如果你的应用是一个模块,那么这个文件夹就位于模块旁边;如果你的应用是一个包,那么它实际上位于包内部:

情况 1: 一个模块:

复制代码
/application.py
/templates
    /hello.html

情况 2: 一个包:

复制代码
/application
    /__init__.py
    /templates
        /hello.html

对于模板,您可以充分利用 Jinja2 模板的全部功能。更多信息,请访问官方的 Jinja2 模板文档 .

以下是一个示例模板:

复制代码
<!doctype html>
<title>Hello from Flask</title>
{% if person %}
  <h1>Hello {{ person }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

在模板内部,您还可以访问 config, request, sessiong [1] 对象以及:func:~flask.url_forget_flashed_messages() 函数。

如果使用继承,模板会特别有用。如果你想了解它的工作原理,请参阅 模板继承 。基本上,模板继承使得在每个页面上保留某些元素(例如页眉、导航和页脚)成为可能。

转义已自动启用,因此如果 person 包含 HTML,它将被自动转义。如果您信任某个变量,并且您知道它是安全的 HTML(例如,因为它来自将 wiki 标记语言转换为 HTML 的模块),您可以使用 Markup 类或使用模板中的 |safe 过滤器将其标记为安全。更多示例,请参阅 Jinja 2 文档。

以下是对 Markup 类工作原理的基本介绍:

复制代码
>>> from markupsafe import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup('<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
'Marked up » HTML'

一、先搞懂:"渲染模板" 到底在做什么?

简单说,"渲染模板" 就是Flask 把 Python 里的数据,填充到提前写好的 HTML 模板里,最终生成用户能看到的完整网页。举个具体例子:

  • 你在 Python 里定义了一个变量 name = "小明"
  • 你提前写好一个 HTML 模板(hello.html),里面留了个 "空位" Hello, {``{ person }}!
  • render_template("hello.html", person=name) 这句话,Flask 会自动把 name 的值 "填" 进 HTML 的 "空位" 里,生成最终的 HTML 内容:Hello, 小明!,再返回给浏览器展示。

二、为什么要这么做?(解决 "直接写 HTML" 的麻烦)

如果不用模板,你得在 Python 里直接写 HTML 字符串,比如:

复制代码
# 不用模板的麻烦写法
@app.route('/hello/<name>')
def hello(name):
    # 把HTML和Python变量混在一起,写起来又长又容易错
    return f"<h1>Hello, {name}!</h1><p>今天天气很好</p>"

这种写法有 3 个大问题:

  1. 代码混乱:Python 逻辑和 HTML 样式混在一起,改个页面样式都要动 Python 代码;
  2. 安全风险 :如果用户输入特殊字符(比如 <script>),直接拼接会导致安全漏洞,而模板会自动帮你 "转义"(把危险字符变成无害的);
  3. 复杂页面搞不定:如果页面有表单、列表、循环展示(比如展示 10 条新闻),直接写 HTML 字符串会非常繁琐,模板却能轻松处理。

三、怎么用?(3 个关键步骤,照着做就行)

用模板的核心是 "按规则放文件 + 正确调用",分 3 步:

步骤 1:先建对 "模板文件夹"(位置不能错)

Flask 有个固定规则:所有 HTML 模板必须放在名叫 templates 的文件夹里,文件夹的位置分两种情况(看你的项目是 "模块" 还是 "包"):

你可以试着按上面的步骤建个简单的模板,运行后访问 http://127.0.0.1:5000/hello/小明,就能直观看到效果了~

  • **情况 1:项目是单个 Python 文件(模块)**文件夹和 Python 文件 "平级",结构像这样:

    复制代码
    你的项目文件夹/
    ├─ application.py  # 你的Flask代码文件
    └─ templates/       # 必须叫这个名字
         └─ hello.html  # 你的HTML模板文件

    情况 2:项目是多个文件(包,比如有 init.py) templates 要放在包文件夹里面,结构像这样:

    复制代码
    你的项目文件夹/
    └─ application/     # 包文件夹
         ├─ __init__.py # 包的初始化文件(里面写Flask app)
         └─ templates/  # 模板文件夹放在包里面
              └─ hello.html

    步骤 2:写 HTML 模板(留 "空位" 给 Python 数据)

    templates 里新建 hello.html,用 {``{ 变量名 }} 留 "空位",比如:

    复制代码
    <!-- hello.html 内容 -->
    <!DOCTYPE html>
    <html>
    <head>
        <title>欢迎页</title>
        <style>/* 这里写CSS样式,美化页面 */
            h1 { color: blue; }
        </style>
    </head>
    <body>
        <!-- {{ person }} 就是留给Python数据的"空位" -->
        <h1>Hello, {{ person }}!</h1>
        <!-- 还能放更复杂的逻辑,比如循环(模板自带语法) -->
        <p>你喜欢的水果:</p>
        <ul>
            {% for fruit in fruit_list %}  <!-- 模板的循环语法 -->
                <li>{{ fruit }}</li>
            {% endfor %}
        </ul>
    </body>
    </html>

    这里的 {``{ person }}{% for ... %} 是 Jinja2 模板引擎的语法,专门用来和 Python 数据交互,不用记太多,常用的就 "变量({{}})" 和 "循环 / 判断({% %})"。

    步骤 3:在 Python 里调用模板(传数据过去)

    在 Flask 代码里用 render_template() 函数,把 Python 数据传给模板,比如

    复制代码
    from flask import Flask, render_template
    
    app = Flask(__name__)
    
    @app.route('/hello/')
    @app.route('/hello/<name>')  # 支持带参数,比如 /hello/小红
    def hello(name=None):
        # 1. 准备要传给模板的数据(可以是变量、列表、字典等)
        fruit_list = ["苹果", "香蕉", "橙子"]  # 要在模板里循环展示的列表
        
        # 2. 调用模板:参数1是模板文件名,后面是"模板变量名=Python数据"
        return render_template(
            "hello.html",  # 模板文件名(必须在templates里)
            person=name,   # 把Python的name传给模板的person变量
            fruit_list=fruit_list  # 把列表传给模板的fruit_list变量
        )
    
    if __name__ == '__main__':
        app.run()

    总结一下

  • 核心逻辑:Python 管数据和逻辑,HTML 模板管页面展示,Flask 负责把两者 "拼" 起来

  • 关键规则:模板放 templates 文件夹,用 {``{ 变量 }} 留空位,render_template() 传数据;

  • 好处:代码整洁、安全、能做复杂页面(比如循环展示列表、判断显示内容)。

8.访问请求数据

对于 Web 应用来说,响应客户端发送到服务器的数据至关重要。在 Flask 中,这些信息由全局的 request 对象提供。如果您有使用 Python 的经验,您可能会好奇这个对象怎么能是全局的,以及 Flask 是如何做到多线程安全的。答案是上下文局部变量:

上下文局部变量

内部信息

如果您想了解其工作原理以及如何使用上下文局部变量实现测试,请阅读本节,否则请跳过它。

Flask 中的某些对象是全局对象,但并非通常意义上的全局对象。这些对象实际上是特定上下文中局部对象的代理。听起来有点拗口,但其实很容易理解。

想象一下,上下文就是在处理请求的线程。一个请求到来,Web 服务器决定创建一个新线程(或者能够处理除线程之外的并发系统的其他底层对象)。当 Flask 启动其内部请求处理时,它会判断当前线程是活动上下文,并将当前应用程序和 WSGI 环境绑定到该上下文(线程)。它以一种智能的方式执行此操作,以便一个应用程序可以调用另一个应用程序而不会中断。

那么这对你意味着什么?基本上,除非你正在进行单元测试之类的工作,否则你完全可以忽略这种情况。你会注意到,依赖于请求对象的代码会突然出错,因为没有请求对象。解决方案是自己创建一个请求对象并将其绑定到上下文。单元测试最简单的解决方案是使用 test_request_context`上下文管理器。结合 ``with`() 语句,它会绑定一个测试用请求,以便你能够与其交互。以下是一个例子:

复制代码
from flask import request

with app.test_request_context('/hello', method='POST'):
    # now you can do something with the request until the
    # end of the with block, such as basic assertions:
    assert request.path == '/hello'
    assert request.method == 'POST'

另一种可能性是将整个 WSGI 环境传递给 request_context() 方法:

复制代码
with app.request_context(environ):
    assert request.method == 'POST'

请求对象

请求对象已在 API 部分中记录,我们在此不再赘述(参见 Request )。以下是对一些最常见操作的概述。首先,您需要从 flask 模块导入它:

复制代码
from flask import request

当前的请求方法可以通过 method 属性获取。要访问表单数据(通过 POSTPUT 请求传输的数据),可以使用 form 属性。以下是上述两个属性的完整示例:

复制代码
@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)

如果 form 属性中不存在该键,会发生什么情况?在这种情况下,会引发一个特殊的 KeyError 错误。您可以像捕获正常 KeyError 错误一样捕获它,但如果不这样做,则会显示 HTTP 400 Bad Request 错误页面。因此,在很多情况下,您无需处理这个问题。

要访问 URL 中提交的参数(?key=value),您可以使用 args 属性:

复制代码
searchword = request.args.get('key', '')

我们建议使用 get 或捕获 KeyError 来访问 URL 参数,因为用户可能会更改 URL,在这种情况下向他们显示 400 错误请求页面并不方便用户使用。

有关请求对象的方法和属性的完整列出,请转到 Request 文档。

文件上传

你可以使用 Flask 轻松处理上传的文件。只需确保不要忘记在 HTML 表单上设置 enctype="multipart/form-data" 属性,否则浏览器将根本不会传输你的文件。

上传的文件存储在内存中或文件系统的一个临时位置。您可以通过查看请求对象上的 files 属性来访问这些文件。每个上传的文件都存储在该字典中。它的行为类似于标准的 Python file 对象,但它还具有一个 save() 方法,允许您将该文件存储在服务器的文件系统上。以下是一个简单的示例,展示了它的工作原理:

复制代码
from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

如果您想知道文件在上传到你的应用程序之前在客户端是如何命名的,可以访问 filename 属性。但请记住,此值可以被伪造,所以永远不要相信它。如果您想使用客户端的文件名将文件存储在服务器上,请将其传递给 Werkzeug 提供的 secure_filename() 函数:

复制代码
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['the_file']
        file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
    ...

浏览器 Cookies

要访问 Cookie,您可以使用 cookies 属性。要设置 Cookie,您可以使用响应对象的 set_cookie 方法。请求对象的 cookies 属性是一个字典,其中包含用户端传输的所有 Cookie。如果您想使用sessions,请不要直接使用 Cookie,而是使用 Flask 中的 会话 ,它会在 Cookie 的基础上为您增加一些安全性。

读取 Cookies:

复制代码
from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

存储 Cookies:

复制代码
from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

请注意,Cookie 是设置在响应对象上的。由于您通常只是从视图函数返回字符串,因此 Flask 会将它们转换为响应对象。如果您想要显式地这样做,可以使用 make_response() 函数,然后对其进行修改。

有时你可能想在响应对象尚不存在时设置 cookie。这可以通过使用 延迟执行的请求回调 模式来实现。

9.重定向和错误

要将用户重定向到另一个端点,请使用 redirect() 函数;要带着一个错误代码提前中止请求,请使用 abort() 函数:

复制代码
from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

这是一个毫无意义的例子,因为用户将从索引被重定向到一个他们无法访问的页面(401 表示拒绝访问),但它展示了其工作原理。

默认情况下,每个错误代码都会显示一个黑白的错误页面。如果要自定义错误页面,可以使用 errorhandler() 装饰器:

复制代码
from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

注意 render_template() 调用后面的 404 。这告诉 Flask 该页面的状态码应该是 404,表示未找到。默认情况下,状态码为 200,翻译过来就是:一切顺利。

10.关于响应

视图函数的返回值会自动转换为响应对象。如果返回值是字符串,则会转换为一个包含字符串作为响应主体、 200 OK 状态码和 text/html MIME 类型的响应对象。如果返回值是字典或列表,则会调用 jsonify() 来生成响应。Flask 将返回值转换为响应对象的逻辑如下:

  1. 如果返回了类型正确的响应对象,则它将直接从视图返回。

  2. 如果它是一个字符串,则使用该数据和默认参数创建一个响应对象。

  3. 如果它是一个返回字符串或bytes的迭代器或生成器,则它将被视为流响应。

  4. 如果它是一个字典或列表,则使用 jsonify() 创建一个响应对象。

  5. 如果返回的是元组,则元组中的项可以提供额外的信息。此类元组的格式必须为 (response, status), (response, headers), 或 (response, status, headers)status 值将覆盖状态码, headers 可以是包含附加HTTP标头值的列表或字典。

  6. 如果这些都不符合,Flask 将假定返回值是一个有效的 WSGI 应用程序并将其转换为响应对象。

如果您想要在视图中获取响应对象的结果,您可以使用 make_response() 函数。

想象一下你有一个这样的视图函数:

复制代码
from flask import render_template

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

您只需要用 make_response() 包装返回表达式然后获取响应对象来修改它,最后返回它:

复制代码
from flask import make_response

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

JSON 格式的 API

编写 API 时,JSON 是一种常见的响应格式。使用 Flask 编写这样的 API 非常简单。如果你从视图返回一个 dictlist ,它将被转换为 JSON 响应。

复制代码
@app.route("/me")
def me_api():
    user = get_current_user()
    return {
        "username": user.username,
        "theme": user.theme,
        "image": url_for("user_image", filename=user.image),
    }

@app.route("/users")
def users_api():
    users = get_all_users()
    return [user.to_json() for user in users]

这是将数据传递给 jsonify() 函数的快捷方式,该函数将序列化任何支持的 JSON 数据类型。这意味着字典或列表中的所有数据都必须是可 JSON 序列化的。

对于数据库模型等复杂类型,您需要先使用序列化库将数据转换为有效的 JSON 类型。Flask 社区维护着许多序列化库和 Flask API 扩展,它们支持更复杂的应用程序。

11.会话

除了请求对象之外,还有一个名为 session 的对象,它允许你在几个请求间存储特定于用户的信息。它基于 cookie 实现,并对 cookie 进行加密签名。这意味着用户可以查看你的 cookie 内容,但无法修改,除非他们知道用于签名的密钥。

要使用sessions,您必须设置一个密钥。session的工作原理如下:

复制代码
from flask import session

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}'
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

如何生成好的密钥

密钥应该尽可能随机。你的操作系统有办法基于加密随机生成器生成相当随机的数据。使用以下命令可以快速生成用于 Flask.secret_key (或 SECRET_KEY)的值:

复制代码
$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

关于基于 Cookie 的sessions的注意事项:Flask 会将您传入session对象的值序列化为 Cookie。如果您发现某些值在请求之间无法保持,且 Cookie 确实已启用,而您没有收到清晰的错误消息,请检查页面响应中 Cookie 的大小,并将其与 Web 浏览器支持的大小进行比较。

除了默认的基于用户端的sessions之外,如果您想在服务器端处理sessions,有几个 Flask 扩展可以支持此功能。

12.消息闪现

好的应用程序和用户界面都离不开反馈。如果用户得不到足够的反馈,他们最终可能会讨厌这个应用程序。Flask 的消息闪现系统提供了一种非常简单的方法来向用户提供反馈。消息闪现系统基本上是在一次请求末尾中存储消息,并在下一次请求中显示的机制。这通常与布局模板结合使用来显示消息。

要闪现消息,请使用 flash() 方法;要获取消息,可以使用 get_flashed_messages() 方法,该方法在模板中也可用。完整示例请参阅 消息闪现

13.日志记录

Changelog

有时您可能会遇到这样的情况:您处理的数据应该正确,但实际上却不正确。例如,您可能有一些用户端代码向服务器发送 HTTP 请求,但该请求显然格式错误。这可能是由于用户篡改了数据,或者用户端代码出现故障造成的。大多数情况下,在这种情况下回复 400 Bad Request 是可以的,但有时这样做是不行的,代码必须继续工作。

你可能仍然想记录下有一些可疑的事情发生。这时,记录器就派上用场了。从 Flask 0.3 开始会预配置一个记录器供你使用。

以下是一些日志调用示例:

复制代码
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

附加的 logger 是一个标准日志记录器 Logger,因此请转到官方 logging 文档以获取更多信息。

参见 处理程序错误

14.添加WSGI中间件

要将 WSGI 中间件添加到 Flask 应用,请包装应用的 wsgi_app 属性。例如,要应用 Werkzeug 的 ProxyFix 中间件来在 Nginx 后运行:

复制代码
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

包装 app.wsgi_app 而不是 app 意味着 app 仍然指向您的Flask应用程序,而不是中间件,因此您可以继续直接使用和配置 app

15.使用 Flask 扩展

扩展是一些可帮助您完成常见任务的软件包。例如,Flask-SQLAlchemy 提供了 SQLAlchemy 支持,让您能够轻松便捷地将其与 Flask 配合使用。

有关 Flask 扩展的更多信息,请参阅 扩展

16.部署到 Web 服务器

准备好部署你的新 Flask 应用了吗?请参阅 部署到生产环境

相关推荐
我的xiaodoujiao几秒前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 27--二次封装方法--优化断言结果
python·学习·测试工具·pytest
q***d17316 分钟前
Rust并发模型
开发语言·后端·rust
j***121518 分钟前
springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice
spring boot·后端·docker
love530love18 分钟前
解决 ComfyUI 启动显示 ‘sox‘ 命令未找到错误:从安装到配置的完整指南
人工智能·windows·python·aigc·comfyui·comfyui-manager
资深web全栈开发38 分钟前
Golang Cobra 教程:构建强大的CLI应用
开发语言·后端·golang
u***276138 分钟前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
J***79391 小时前
Python在机器学习中的数据处理
开发语言·python·机器学习
深蓝电商API1 小时前
初级爬虫反爬应对:解决 403、IP 限制的简单方法
爬虫·python
闲人编程2 小时前
Python协程的演进:从yield到async/await的完整历史
java·前端·python·async·yield·await·codecapsule