快速入门

急于开始?本页将对 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 代码。它受密码保护,但仍然存在重大的安全风险。不要在生产环境中运行开发服务器或调试器。

要启用调试模式,请使用 --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,用户更有可能喜欢该页面并再次访问该页面,并可直接使用该 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', name=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 name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

在模板中,您还可以访问 configrequestsessiong [1] 对象以及 url_for()get_flashed_messages() 函数。

如果使用继承,模板会特别有用。如果您想了解其工作原理,请参阅 模板继承。基本上,模板继承可以使每个页面上保持某些元素(如页眉、导航和页脚)。

启用了自动转义,因此如果 name 包含 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 属性。但是请记住,此值可以伪造,因此切勿信任该值。如果您想使用客户端的文件名在服务器上存储文件,请通过 secure_filename() 函数传递它,Werkzeug 为您提供了此函数

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)}")
    ...

有关一些更好的示例,请参见 上传文件

Cookie

要访问 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 MIME 类型。如果返回值是一个字典或列表,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 应用程序?请参见 部署到生产环境