快速入门 — Flask 文档
快速入门
急于开始? 这个页面很好地介绍了 Flask。 按照【X7X】安装【X23X】设置项目,先安装Flask。
一个最小的应用程序
一个最小的 Flask 应用程序看起来像这样:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
那么这段代码做了什么?
- 首先我们导入了
Flask
类。 此类的一个实例将是我们的 WSGI 应用程序。 - 接下来我们创建这个类的一个实例。 第一个参数是应用程序的模块或包的名称。
__name__
是一个方便的快捷方式,适用于大多数情况。 这是必需的,以便 Flask 知道在哪里寻找资源,例如模板和静态文件。 - 然后我们使用
route()
装饰器告诉 Flask 哪个 URL 应该触发我们的函数。 - 该函数返回我们想要在用户浏览器中显示的消息。 默认内容类型为 HTML,因此字符串中的 HTML 将由浏览器呈现。
将其另存为 hello.py
或类似内容。 确保不要调用您的应用程序 flask.py
,因为这会与 Flask 本身发生冲突。
要运行该应用程序,请使用 flask 命令或 python -m flask。 在执行此操作之前,您需要通过导出 FLASK_APP
环境变量来告诉终端要使用的应用程序:
这将启动一个非常简单的内置服务器,它足以进行测试,但可能不是您想要在生产中使用的。 有关部署选项,请参阅 部署选项 。
现在转到 http://127.0.0.1:5000/,您应该会看到您的 hello world 问候语。
如果另一个程序已经在使用端口 5000,当服务器尝试启动时,您将看到 OSError: [Errno 98]
或 OSError: [WinError 10013]
。 请参阅 Address already in use 了解如何处理。
外部可见服务器
如果您运行服务器,您会注意到该服务器只能从您自己的计算机访问,而不能从网络中的任何其他计算机访问。 这是默认设置,因为在调试模式下,应用程序的用户可以在您的计算机上执行任意 Python 代码。
如果您禁用了调试器或信任您网络上的用户,您只需在命令行中添加 --host=0.0.0.0
即可使服务器公开可用:
$ flask run --host=0.0.0.0
这会告诉您的操作系统侦听所有公共 IP。
如果服务器没有启动怎么办
如果 python -m flask 失败或 flask 不存在,可能有多种原因。 首先,您需要查看错误消息。
旧版本的烧瓶
早于 0.11 的 Flask 版本曾经有不同的方式来启动应用程序。 简而言之,flask 命令不存在,python -m flask 也不存在。 在这种情况下,您有两个选择:升级到更新的 Flask 版本或查看 Development Server 以查看运行服务器的替代方法。
无效的导入名称
FLASK_APP
环境变量是要在 flask run 中导入的模块的名称。 如果模块命名不正确,您将在启动时收到导入错误(或者如果导航到应用程序时启用了调试)。 它会告诉您它尝试导入的内容以及失败的原因。
最常见的原因是打字错误或因为您实际上没有创建 app
对象。
调试模式
flask run
命令可以做的不仅仅是启动开发服务器。 通过启用调试模式,服务器将在代码更改时自动重新加载,并在请求期间发生错误时在浏览器中显示交互式调试器。
class=screenshot align-center|The interactive debugger in action.
警告
调试器允许从浏览器执行任意 Python 代码。 它受 PIN 保护,但仍存在重大安全风险。 不要在生产环境中运行开发服务器或调试器。
要启用所有开发功能,请在调用 flask run
之前将 FLASK_ENV
环境变量设置为 development
。
另请参阅
- Development Server 和 Command Line Interface 有关在开发模式下运行的信息。
- 调试应用程序错误 了解有关使用内置调试器和其他调试器的信息。
- Logging 和 Handling Application Errors 记录错误并显示漂亮的错误页面。
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 会将您重定向到带有尾部斜杠 (/projects/
) 的规范 URL。
about
端点的规范 URL 没有尾部斜杠。 它类似于文件的路径名。 访问带有斜杠 (/about/
) 的 URL 会产生 404“未找到”错误。 这有助于保持这些资源的 URL 唯一,这有助于搜索引擎避免将同一页面索引两次。
网址构建
要构建特定函数的 URL,请使用 url_for()
函数。 它接受函数名作为它的第一个参数和任意数量的关键字参数,每个参数对应于 URL 规则的一个可变部分。 未知变量部分作为查询参数附加到 URL。
为什么要使用 URL 反向函数 url_for()
来构建 URL,而不是将它们硬编码到模板中?
- 反转通常比硬编码 URL 更具描述性。
- 您可以一次性更改您的 URL,而无需记住手动更改硬编码的 URL。
- URL 构建透明地处理特殊字符的转义。
- 生成的路径总是绝对的,避免了浏览器中相对路径的意外行为。
- 如果您的应用程序位于 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()
如果存在 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 模板引擎。
要渲染模板,您可以使用 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 %}
在模板中,您还可以访问 config
、request、session 和 g 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 <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
'Marked up » HTML'
0.5 版更改: 不再为所有模板启用自动转义。 以下模板扩展会触发自动转义:.html
、.htm
、.xml
、.xhtml
。 从字符串加载的模板将禁用自动转义。
- 1
- 不确定 g 对象是什么? 它是您可以根据自己的需要存储信息的东西。 请参阅 flask.g 和 Using SQLite 3 with Flask 的文档。
访问请求数据
对于 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
属性获得。 要访问表单数据(在 POST
或 PUT
请求中传输的数据),您可以使用 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 错误请求错误页面。 因此,在许多情况下,您不必处理该问题。
要访问在 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)}")
...
有关一些更好的示例,请参阅 上传文件 。
重定向和错误
要将用户重定向到另一个端点,请使用 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。 如果返回值是 dict,则调用 jsonify()
以产生响应。 Flask 将返回值转化为响应对象的逻辑如下:
- 如果返回正确类型的响应对象,它将直接从视图返回。
- 如果它是一个字符串,则使用该数据和默认参数创建一个响应对象。
- 如果是 dict,则使用
jsonify
创建响应对象。 - 如果返回元组,元组中的项目可以提供额外信息。 此类元组必须采用
(response, status)
、(response, headers)
或(response, status, headers)
的形式。status
值将覆盖状态代码,headers
可以是附加标题值的列表或字典。 - 如果这些都不起作用,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 很容易。 如果您从视图返回 dict
,它将被转换为 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),
}
根据您的 API 设计,您可能希望为 dict
以外的类型创建 JSON 响应。 在这种情况下,请使用 jsonify()
函数,该函数将序列化任何支持的 JSON 数据类型。 或者查看支持更复杂应用程序的 Flask 社区扩展。
from flask import jsonify
@app.route("/users")
def users_api():
users = get_all_users()
return jsonify([user.to_json() for user in users])
会话
除了请求对象之外,还有一个名为 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 扩展的更多信息,请参阅 扩展 。