快速上手
等久了吧?本文会给您好好介绍如何上手 Flask 。这里假定您已经安装好了 Flask ,否则请先阅读《安装 》。
# 1.4.1 一个最小的应用
一个最小的 Flask 应用如下:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
2
3
4
5
6
7
那么,这些代码是什么意思呢?
- 首先我们导入了
Flask类。该类的实例将会成为我们的WSGI应用。 - 接着我们创建一个该类的实例。第一个参数是应用模块或者包的名称。
__name__是一个适用于大多 数情况的快捷方式。有了这个参数,Flask才能知道在哪里可以找到模板和静态文件等东西。 - 然后我们使用
route()装饰器来告诉Flask触发函数的URL。 - 函数返回需要在用户浏览器中显示的信息。默认的内容类型是
HTML,因此字符串中的HTML会被浏 览器渲染。
把它保存为 hello.py 或其他类似名称。请不要使用 flask.py 作为应用名称,这会与 Flask 本身发生冲突。
可以使用 flask 命令或者 python 的 -m 开关来运行这个应用。在运行应用之前,需要在终端里导出 FLASK_APP 环境变量:
$ export FLASK_APP=hello
$ flask run
* Running on http://127.0.0.1:5000/
2
3
> set FLASK_APP=hello
> flask run
* Running on http://127.0.0.1:5000/
2
3
> $env:FLASK_APP = "hello"
> flask run
* Running on http://127.0.0.1:5000/
2
3
// Make sure to add code blocks to your code group
应用发现行为
作为一个捷径,如果文件名为 app.py 或者 wsgi.py ,那么您不需要设置 FLASK_APP 环境变量。详见命 令行接口 。
这样就启动了一个非常简单的内建的服务器。这个服务器用于测试应该是足够了,但是用于生产可能是不够 的。关于部署的有关内容参见部署方式 。
现在在浏览器中打开 http://127.0.0.1:5000/ ,应该可以看到 Hello World! 字样。
外部可见的服务器
运行服务器后,会发现只有您自己的电脑可以使用服务,而网络中的其他电脑却不行。缺省设置就是这样的, 因为在调试模式下该应用的用户可以执行您电脑中的任意 Python 代码。
如果您关闭了调试器或信任您网络中的用户,那么可以让服务器被公开访问。只要在命令行上简单的加上 --host=0.0.0.0 即可:
$ flask run --host=0.0.0.0
这行代码告诉您的操作系统监听所有公开的 IP 。
# 1.4.2 如果服务器不能启动怎么办
假如运行 python -m flask 命令失败或者 flask 命令不存在,那么可能会有多种原因导致失败。首先应 该检查错误信息。
老版本的 Flask
版本低于 0.11 的 Flask ,启动应用的方式是不同的。简单的说就是 flask 和 python -m flask 命令都无 法使用。在这种情况下有两个选择:一是升级 Flask 到更新的版本,二是参阅开发服务器 ,学习其他启动服 务器的方法。
非法导入名称
FLASK_APP 环境变量中储存的是模块的名称,运行 flask run 命令就会导入这个模块。如果模块的名称 不对,那么就会出现导入错误。出现错误的时机是在应用开始的时候。如果调试模式打开的情况下,会在运 行到应用开始的时候出现导入错误。出错信息会告诉您尝试导入哪个模块时出错,为什么会出错。
最常见的错误是因为拼写错误而没有真正创建一个 app 对象。
# 1.4.3 调试模式
flask run 命令不只可以启动开发服务器。如果您打开调试模式,那么服务器会在修改应用代码之后自动 重启,并且当请求过程中发生错误时还会在浏览器中提供一个交互调试器。

注意
调试器允许执行来自浏览器的任意 Python 代码。虽然它由一个 pin 保护,但仍然存在巨大安 全风险。不要在生产环境中运行开发服务器或调试器。
如果需要打开所有开发功能,那么需要在运行 flask run 之前设置 FLASK_ENV 环境变量为 development 。
$ export FLASK_ENV=development
$ flask run
2
> set FLASK_ENV=development
> flask run
2
> $env:FLASK_ENV = "development"
> flask run
2
// Make sure to add code blocks to your code group
另见:
开发服务器和命令行接口包含有关开发模式运行的内容。调试应用程序错误包含有关内置调试器和其他调试器的内容。日志和应用错误处理包含有关日志记录和显示友好的出错信息页面的内容
# 1.4.4 HTML 转义
当返回 HTML (Flask 中的默认响应类型)时,为了防止注入攻击,所有用户提供的值在输出渲染前必须被 转义。使用 Jinja (这个稍后会介绍)渲染的 HTML 模板会自动执行此操作。
在下面展示的 escape() 可以手动转义。因为保持简洁的原因,在多数示例中它被省略了,但您应该始终留 心处理不可信的数据。
from markupsafe import escape
@app.route("/<name>")
def hello(name):
return f"Hello, {escape(name)}!"
2
3
4
5
如果一个用户想要提交其名称为 <script>alert("bad")</script>,那么宁可转义为文本,也好过在 浏览器中执行脚本。
路由中的从<name>URL 中捕获值并将其传递给视图函数。这些变量规则见下文。
# 1.4.5 路由
现代 web 应用都使用有意义的 URL ,这样有助于用户记忆,网页会更得到用户的青睐,提高回头率。 使用route() 装饰器来把函数绑定到 URL:
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello, World'
2
3
4
5
6
7
但是能做的不仅仅是这些!您可以动态变化 URL 的某些部分,还可以为一个函数指定多个规则。
变量规则
通过把 URL 的一部分标记为 就可以在 URL 中添加变量。标记的部分会作为关键字参 数传递给函数。通过使用 ,可以选择性的加上一个转换器,为变量指定规 则。请看下面的例子:
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)}'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
转换器类型:
| 类型 | 注释 |
|---|---|
| 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'
2
3
4
5
6
7
projects 的 URL 是中规中矩的,尾部有一个斜杠,看起来就如同一个文件夹。访问一个没有斜杠结尾的 URL (/projects )时 Flask 会自动进行重定向,帮您在尾部加上一个斜杠(/projects/ )。
about 的 URL 没有尾部斜杠,因此其行为表现与一个文件类似。如果访问这个 URL 时添加了尾部斜杠(/about/ )就会得到一个 404 “未找到”错误。这样可以保持 URL 唯一,并有助于搜索引擎重复索引同一页 面。
URL 构建
url_for() 函数用于构建指定函数的 URL。它把函数名称作为第一个参数。它可以接受任意个关键字参数, 每个关键字参数对应 URL 中的变量。未知变量将添加到 URL 中作为查询参数。
为什么不在把 URL 写死在模板中,而要使用反转函数url_for() 动态构建?
笔记
- 反转通常比硬编码
URL的描述性更好。 - 您可以只在一个地方改变
URL,而不用到处乱找。 URL创建会为您处理特殊字符的转义,比较直观。- 生产的路径总是绝对路径,可以避免相对路径产生副作用。
- 如果您的应用是放在 URL 根路径之外的地方(如在
/myapplication中,不在 / 中),url_for()会为您妥善处理。
例 如, 这 里 我 们 使 用test_request_context() 方 法 来 尝 试 使 用url_for() 。 test_request_context() 告诉 Flask 正在处理一个请求,而实际上也许我们正处在交互 Python shell 之中,并没有真正的请求。参见本地环境 。
from flask import url_for
app = Flask(__name__)
@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'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/
/login
/login?next=/
/user/John%20Doe
2
3
4
HTTP 方法
Web 应用使用不同的 HTTP 方法处理 URL 。当您使用 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()
2
3
4
5
6
7
8
如果当前使用了 GET 方法,Flask 会自动添加 HEAD 方法支持,并且同时还会按照 HTTP RFC 来处理 HEAD 请求。同样,OPTIONS 也会自动实现。
# 1.4.6 静态文件
动态的 web 应用也需要静态文件,一般是 CSS 和 JavaScript 文件。理想情况下您的服务器已经配置好了为您 的提供静态文件的服务。但是在开发过程中,Flask 也能做好这项工作。只要在您的包或模块旁边创建一个名 为 static 的文件夹就行了。静态文件位于应用的 /static 中。
使用特定的 'static' 端点就可以生成相应的 URL
url_for('static', filename='style.css')
这个静态文件在文件系统中的位置应该是 static/style.css 。
# 1.4.7 渲染模板
在 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)
2
3
4
5
6
Flask 会在 templates 文件夹内寻找模板。因此,如果您的应用是一个模块,那么模板文件夹应该在模块旁 边;如果是一个包,那么就应该在包里面:
情形 1 : 一个模块:
/application.py
/templates
/hello.html
2
3
情形 2 : 一个包:
/application
/__init__.py
/templates
/hello.html
2
3
4
您可以充分使用 Jinja2 模板引擎的威力。更多内容,详见官方 Jinja2 模板文档 (opens new window) 。 模板示例:
<!DOCTYPE html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
2
3
4
5
6
7
在模板内部可以像使用url_for() 和get_flashed_messages() 函数一样访问config 、request 、 session 和g 1 对象。
模板在继承使用的情况下尤其有用。其工作原理参见模板继承 。简单的说,模板继承可以使每个页面的特定 元素(如页头、导航和页尾)保持一致。
自动转义默认开启。因此,如果 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 \xbb HTML'
2
3
4
5
6
7
注意
Changed in version 0.5: 自动转义不再为所有模板开启,只为扩展名为 .html 、.htm 、.xml 和 .xhtml 开 启。从字符串载入的模板会关闭自动转义。
# 1.4.8 操作请求数据
对于 web 应用来说对客户端向服务器发送的数据作出响应很重要。在 Flask 中由全局对象request 来提供请 求信息。如果您有一些 Python 基础,那么可能会奇怪:既然这个对象是全局的,怎么还能保持线程安全?答 案是本地环境:
本地环境
内部信息
如果您想了解工作原理和如何使用本地环境进行测试,那么请阅读本节,否则可以跳过本节。
某些对象在 Flask 中是全局对象,但不是通常意义下的全局对象。这些对象实际上是特定环境下本地对象的 代理。真拗口!但还是很容易理解的。
设想现在处于处理线程的环境中。一个请求进来了,服务器决定生成一个新线程(或者叫其他什么名称的东 西,这个下层的东西能够处理包括线程在内的并发系统)。当 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'
2
3
4
5
6
另一种方式是把整个 WSGI 环境传递给request_context() 方法:
with app.request_context(environ):
assert request.method == 'POST'
2
请求对象
请求对象在 API 一节中有详细说明这里不细谈(参见Request )。这里简略地谈一下最常见的操作。首先, 您必须从 flask 模块导入请求对象:
from flask import request
通过使用method 属性可以操作当前请求方法,通过使用form 属性处理表单数据(在 POST 或者 PUT 请求 中传输的数据)。以下是使用上述两个属性的例子:
@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)
2
3
4
5
6
7
8
9
10
11
当 form 属性中不存在这个键时会发生什么?会引发一个 KeyError 。如果您不像捕捉一个标准错误一样捕 捉 KeyError ,那么会显示一个 HTTP 400 Bad Request 错误页面。因此,多数情况下您不必处理这个问题。
要操作 URL (如 ?key=value )中提交的参数可以使用args 属性:
searchword = request.args.get('key', '')
用户可能会改变 URL 导致出现一个 400 请求出错页面,这样降低了用户友好度。因此,我们推荐使用 get 或 通过捕捉 KeyError 来访问 URL 参数。
完整的请求对象方法和属性参见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')
...
2
3
4
5
6
7
8
如果想要知道文件上传之前其在客户端系统中的名称,可以使用 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)}")
...
2
3
4
5
6
7
8
更好的例子参见上传文件 。
Cookies
要访问 cookies ,可以使用cookies 属性。可以使用响应对象的set_cookie 方法来设置 cookies 。请求对 象的cookies 属性是一个包含了客户端传输的所有 cookies 的字典。在 Flask 中,如果使用会话 ,那么就不 要直接使用 cookies ,因为会话 比较安全一些。 读取 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.
2
3
4
5
6
7
储存 cookies:
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
2
3
4
5
6
注意,cookies 设置在响应对象上。通常只是从视图函数返回字符串,Flask 会把它们转换为响应对象。如果 您想显式地转换,那么可以使用make_response() 函数,然后再修改它。
使用 doc:patterns/deferredcallbacks 方案可以在没有响应对象的情况下设置一个 cookie 。
另见关于响应 。
# 1.4.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()
2
3
4
5
6
7
8
9
10
上例实际上是没有意义的,它让一个用户从索引页重定向到一个无法访问的页面(401 表示禁止访问)。但是 上例可以说明重定向和出错跳出是如何工作的。
缺省情况下每种出错代码都会对应显示一个黑白的出错页面。使用errorhandler() 装饰器可以定制出错 页面:
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
2
3
注意render_template() 后面的 404 ,这表示页面对就的出错代码是 404 ,即页面不存在。缺省情况下 200 表示:一切正常。
详见应用错误处理 。
# 1.4.10 关于响应
视图函数的返回值会自动转换为一个响应对象。如果返回值是一个字符串,那么会被转换为一个包含作为响 应体的字符串、一个 200 OK 出错代码和一个 text/html 类型的响应对象。如果返回值是一个字典,那么 会调用 jsonify() 来产生一个响应。以下是转换的规则:
- 如果视图返回的是一个响应对象,那么就直接返回它。
- 如果返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的响应对象。
- 如果返回的是一个字典,那么调用
jsonify创建一个响应对象。 - 如果返回的是一个元组,那么元组中的项目可以提供额外的信息。元组中必须至少包含一个项目,且项 目应当由 (
response,status) 、(response,headers) 或者 (response,status,headers) 组成。status的值会重载状态代码,headers是一个由额外头部值组成的列表或字典。 - 如果以上都不是,那么 Flask 会假定返回值是一个有效的
WSGI应用并把它转换为一个响应对象。
如果想要在视图内部掌控响应对象的结果,那么可以使用make_response() 函数。
设想有如下视图:
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'), 404
2
3
可以使用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
2
3
4
5
JSON 格式的 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),
}
2
3
4
5
6
7
8
如果 dict 还不能满足需求,还需要创建其他类型的 JSON 格式响应,可以使用jsonify() 函数。该函数会 序列化任何支持的 JSON 数据类型。也可以研究研究 Flask 社区扩展,以支持更复杂的应用。
@app.route("/users")
def users_api():
users = get_all_users()
return jsonify([user.to_json() for user in users])
2
3
4
# 1.4.11 会话
除了请求对象之外还有一种称为session 的对象,允许您在不同请求之间储存信息。这个对象相当于用密 钥签名加密的 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'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
如何生成一个好的密钥
生成随机数的关键在于一个好的随机种子,因此一个好的密钥应当有足够的随机性。操作系统可以有多 种方式基于密码随机生成器来生成随机数据。使用下面的命令可以快捷的为 Flask.secret_key (或 者SECRET_KEY )生成值:
$ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/'
2
基于 cookie 的会话的说明:Flask 会取出会话对象中的值,把值序列化后储存到 cookie 中。在打开 cookie 的 情况下,如果需要查找某个值,但是这个值在请求中没有持续储存的话,那么不会得到一个清晰的出错信息。 请检查页面响应中的 cookie 的大小是否与网络浏览器所支持的大小一致。
除了缺省的客户端会话之外,还有许多 Flask 扩展支持服务端会话。
# 1.4.12 消息闪现
一个好的应用和用户接口都有良好的反馈,否则到后来用户就会讨厌这个应用。Flask 通过闪现系统来提供 了一个易用的反馈方式。闪现系统的基本工作原理是在请求结束时记录一个消息,提供且只提供给下一个请 求使用。通常通过一个布局模板来展现闪现的消息。
flash() 用于闪现一个消息。在模板中,使用get_flashed_messages() 来操作消息。完整的例子参见消 息闪现 。
# 1.4.13 日志
New in version 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')
2
3
logger 是一个标准的 Logger Logger 类,更多信息详见官方的 logging (opens new window) 文档。 参见应用错误处理 。
# 1.4.14 集成 WSGI 中间件
如果想要在应用中添加一个 WSGI 中间件,那么可以用应用的 wsgi_app 属性来包装。例如,假设需要在 Nginx 后面使用 ProxyFix 中间件,那么可以这样做:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)
2
用 app.wsgi_app 来包装,而不用 app 包装,意味着 app 仍旧指向您的 Flask 应用,而不是指向中间件。这 样可以继续直接使用和配置 app 。
# 1.4.15 使用 Flask 扩展
扩展是帮助完成公共任务的包。例如 Flask-SQLAlchemy 为在 Flask 中轻松使用 SQLAlchemy 提供支持。 更多关于 Flask 扩展的内容请参阅扩展 。
# 1.4.16 部署到网络服务器
已经准备好部署您的新 Flask 应用了?请移步部署方式 。