快速入门

渴望开始使用?本页提供了一个关于 Flask 的良好介绍。首先按照 安装 设置项目并安装 Flask。

一个最小的应用

一个最小的 Flask 应用看起来像这样:

from flask import Flask

app = Flask(__name__)

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

那么这段代码做了什么呢?

  1. 首先我们导入了 Flask 类。这个类的实例将是我们的 WSGI 应用。

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

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

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

将其保存为 hello.py 或类似的名称。请确保不要将您的应用命名为 flask.py,因为这会与 Flask 本身冲突。

要运行应用,请使用 flask 命令或 python -m flask。您需要使用 --app 选项告诉 Flask 您的应用在哪里。

$ flask --app hello run
 * Serving Flask app 'hello'
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

应用发现行为

作为快捷方式,如果文件名为 app.pywsgi.py,则不必使用 --app。 有关更多详细信息,请参阅 命令行界面

这会启动一个非常简单的内置服务器,它对于测试来说已经足够好了,但可能不是您想要在生产环境中使用的。有关部署选项,请参阅 部署到生产环境

现在访问 http://127.0.0.1:5000/,您应该会看到您的 “hello world” 问候语。

如果另一个程序已经在使用端口 5000,当服务器尝试启动时,您将看到 OSError: [Errno 98]OSError: [WinError 10013]。 有关如何处理这种情况,请参阅 地址已被占用

外部可见的服务器

如果您运行服务器,您会注意到该服务器只能从您自己的计算机访问,而不能从网络中的任何其他计算机访问。这是默认设置,因为在调试模式下,应用的任何用户都可以在您的计算机上执行任意 Python 代码。

如果您禁用了调试器或信任您网络上的用户,您可以通过在命令行中简单地添加 --host=0.0.0.0 来使服务器公开可用

$ flask run --host=0.0.0.0

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

调试模式

flask run 命令的功能不仅仅是启动开发服务器。通过启用调试模式,如果代码更改,服务器将自动重新加载,并且如果在请求期间发生错误,将在浏览器中显示交互式调试器。

The interactive debugger in action.

警告

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

要启用调试模式,请使用 --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

另请参阅

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 捕获一个值并将其传递给视图函数。这些变量规则将在下面解释。

路由

现代 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 “未找到” 错误。这有助于保持这些资源的 URL 唯一性,这有助于搜索引擎避免两次索引同一个页面。

URL 构建

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

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

  1. 反向通常比硬编码 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()

上面的示例将路由的所有方法都保留在一个函数中,如果每个部分都使用一些公共数据,这将非常有用。

您还可以将不同方法的视图分离到不同的函数中。Flask 为使用 get()post() 等装饰此类路由提供了快捷方式,适用于每个常见的 HTTP 方法。

@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 会自动为您实现。

静态文件

动态 Web 应用也需要静态文件。这通常是 CSS 和 JavaScript 文件来源的地方。理想情况下,您的 Web 服务器配置为为您提供它们,但在开发期间,Flask 也可以做到这一点。只需在您的包中或模块旁边创建一个名为 static 的文件夹,它就可以在应用上的 /static 处访问。

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

url_for('static', filename='style.css')

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

渲染模板

从 Python 中生成 HTML 并不有趣,而且实际上非常麻烦,因为您必须自己进行 HTML 转义以保持应用的安全性。 因此,Flask 为您自动配置了 Jinja2 模板引擎。

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

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

要渲染模板,您可以使用 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 %}

在模板内部,您还可以访问 configrequestsessiong [1] 对象,以及 url_for()get_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'
更新日志

在 0.5 版本中更改: 不再为所有模板启用自动转义。以下模板扩展名会触发自动转义:.html, .htm, .xml, .xhtml。 从字符串加载的模板将禁用自动转义。

访问请求数据

对于 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。 如果您想使用会话,请不要直接使用 Cookie,而是使用 Flask 中的 会话,它为您在 Cookie 之上添加了一些安全性。

读取 Cookie

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.

存储 Cookie

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。 这可以通过利用 延迟请求回调 模式来实现。

有关此内容,另请参阅 关于响应

重定向和错误

要将用户重定向到另一个端点,请使用 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,这意味着:一切顺利。

有关更多详细信息,请参阅 处理应用错误

关于响应

视图函数的返回值会自动转换为响应对象。 如果返回值是字符串,则将其转换为以字符串作为响应正文、200 OK 状态代码和 text/html mimetype 的响应对象。 如果返回值是字典或列表,则调用 jsonify() 以生成响应。 Flask 应用于将返回值转换为响应对象的逻辑如下:

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

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

  3. 如果它是迭代器或生成器,返回字符串或字节,则将其视为流式响应。

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

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

  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 API 扩展,它们支持更复杂的应用。

会话

除了请求对象之外,还有一个名为 session 的第二个对象,它允许您存储特定于用户的信息,从一个请求到下一个请求。 这是在 Cookie 之上为您实现的,并以加密方式对 Cookie 进行签名。 这意味着用户可以查看您的 Cookie 的内容,但不能修改它,除非他们知道用于签名的密钥。

为了使用会话,您必须设置一个密钥。 以下是会话的工作方式

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 的会话的注意事项:Flask 将获取您放入会话对象中的值,并将它们序列化到 Cookie 中。 如果您发现某些值无法跨请求持久存在,Cookie 确实已启用,并且您没有收到明确的错误消息,请检查页面响应中 Cookie 的大小与 Web 浏览器支持的大小相比。

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

消息闪现

好的应用程序和用户界面都与反馈息息相关。如果用户没有得到足够的反馈,他们可能会讨厌这个应用程序。Flask 提供了一种非常简单的方式,通过闪现系统向用户提供反馈。闪现系统基本上可以在请求结束时记录一条消息,并在下一个(且仅下一个)请求中访问它。这通常与布局模板结合使用以显示消息。

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

日志记录

更新日志

在 0.3 版本中添加。

有时您可能会遇到处理应该正确但实际上不正确的数据的情况。例如,您可能有某些客户端代码向服务器发送 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 文档以获取更多信息。

请参阅 处理应用程序错误

挂钩 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

使用 Flask 扩展

扩展是帮助您完成常见任务的软件包。例如,Flask-SQLAlchemy 提供了 SQLAlchemy 支持,使其可以简单方便地与 Flask 一起使用。

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

部署到 Web 服务器

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