flask简述

Flask 是一个基于 Python 开发并且依赖 jinja2 模板和 Werkzeug WSGI 服务的一个微型框架,对于 Werkzeug 本质是 Socket 服务端,其用于接收 http 请求并对请求进行预处理,然后触发 Flask 框架,开发人员基于 Flask 框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助 jinja2 模板来实现对模板的处理,将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

“微” (micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。

默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。

搭建开发环境

pipenv 是基于 pip 的 Python 的包管理工具,它和 pip 的用法很相似,可以看作是 pip 的加强版,它的出现解决了旧的 pip+virtualenv+requirements.txt 的工作方式的弊端。可以说 pipenv 是 pip、pipfile、virtualenv 的结合体,它让包安装、包依赖管理和虚拟环境管理更加方便,使用它可以实现高效的 Python 项目开发工作流。

安装

在 Linux 或 macOS 系统中使用 sudo 以全局安装:

sudo pip install pipenv

查看pipenv版本号:

pipenv --version

创建虚拟环境

在 Python 中,虚拟环境(virtual enviroment)就是隔离的 Python 解释器环境。通过创建虚拟环境,你可以拥有一个独立的 Python 解释器环境。这样做的好处是可以为每一个项目创建独立的Python解释器环境,因为不同的项目常常会依赖不同版本的库或 Python 版本。使用虚拟环境可以保持全局 Python 解释器环境的干净,避免包和版本的混乱,并且可以方便地区分和记录每个项目的依赖,以便在新环境下复现依赖环境。
用以下命令为当前的项目创建虚拟环境:

pipenv install

虚拟环境的目录

这会为当前项目创建一个文件夹,其中包含隔离的 Python 解释器环境,并且安装 pip、wheel、setuptools 等基本的包。如果是下载的已经写好的程序,并且里面包含 Pipfile 文件,那么这个文件中列出的依赖包,会在这个命令执行时一并被安装。默认情况下,Pipenv 会统一管理所有虚拟环境。在 Windows 系统中,虚拟环境文件夹会在 C:\Users\Administrator\.virtualenvs\ 目录下创建;在 Linux 或 macOS 会在 ~/.local/share/virtualenvs/ 目录下创建。如果你想在项目目录内创建虚拟环境文件夹,可以设置环境变量

PIPENV_VENV_IN_PROJECT = True

这时名为 .venv 的虚拟环境文件夹将在项目根目录被创建。当然我们可以通过--three和--two选项来声明虚拟环境中使用的 Python 版本(分别对应Python3和Python2),或是使用--python选项指定具体的版本号。同时要确保对应版本的Python已经安装在电脑中。用以下命令显示激活虚拟环境:

pipenv shell

当执行 pipenv shellpipenv run 命令时,Pipenv 会自动从项目目录下的 .env 文件中加载环境变量。Pipenv会启动一个激活虚拟环境的子 shell ,现在你会发现命令行提示符前添加了虚拟环境名“(虚拟环境名称)$”,比如:

(helloflask-5Pa0ZfZw) $

这说明我们已经成功激活了虚拟环境,现在你的所有命令都会在虚拟环境中执行。当你需要退出虚拟环境时,使用 exit 命令。除了显式地激活虚拟环境,Pipenv 还提供了一个神奇的命令,允许你不显式激活虚拟环境即可在当前项目的虚拟环境中执行命令,比如:

pipenv run python hello.py

这会使用虚拟环境中的Python解释器,而不是全局的Python解释器。事实上,和显式激活/关闭虚拟环境的传统方式相比,pipenv run是更推荐的做法,因为这个命令可以让你在执行操作时不用关心自己是否激活了虚拟环境。

依赖管理

在创建虚拟环境时,如果项目根目录下没有 Pipfile 文件,pipenv install 命令还会在项目文件夹根目录下创建PipfilePipfile.lock 文件,前者用来记录项目依赖包列表,而后者记录了固定版本的详细依赖包列表。当我们使用 Pipen 安装/删除/更新依赖包时,Pipfile 以及 Pipfile.lock 会自动更新。你可以使用 pipenv graph 命令查看当前环境下的依赖情况,或是在虚拟环境中使用 pip list 命令查看依赖列表。当需要在一个新的环境运行程序时,只需要执行 pipenv install 命令。Pipenv 就会创建一个新的虚拟环境,然后自动从 Pipfile 中读取依赖并安装到新创建的虚拟环境中。

Pipenv 会自动帮我们管理虚拟环境,所以在执行 pipenv install 安装 Python 包时,无论是否激活虚拟环境,包都会安装到虚拟环境中。后面我们都将使用 Pipenv 安装包,这相当于在激活虚拟环境的情况下使用 pip 安装包。只有需要在全局环境下安装/更新/删除包,我们才会使用pip。

创建 flask 程序

最小的flask程序在此

from flask import Flask
app  = Flask(__name__)

@app.route('/')
def hello():
    return "<h1>hello flask</h1>"
from flask import Flask
app  = Flask(__name__)

@app.route('/')
def hello():
    return "<h1>hello flask</h1>"

创建程序实例

from flask import Flask
app = Flask(__name__)

传入构造方法的第一个参数是模块或包的名称,我们应该使用特殊变量 __name__ 。Python 会根据所处的模块来赋予 name 变量相应的值,对于我们的程序来说(app.py),这个值为 app 。除此之外,这也会帮助 Flask 在相应的文件夹里找到需要的资源,比如模板和静态文件。

注册路由

@app.route('/')
def hello():
    return "<h1>hello flask</h1>"

在一个 Web 应用里,客户端和服务器上的 Flask 程序的交互可以简单概括为以下几步:

  1. 用户在浏览器输入 URL 访问某个资源。
  2. Flask 接收用户请求并分析请求的 URL。
  3. 为这个 URL 找到对应的处理函数。
  4. 执行函数并生成响应,返回给浏览器。
  5. 浏览器接收并解析响应,将信息显示在页面中。

在上面这些步骤中,大部分都由 Flask 完成,我们要做的只是建立处理请求的函数,并为其定义对应的 URL 规则。只需为函数附加 app.route() 装饰器,并传入 URL 规则作为参数,我们就可以让 URL 与函数建立关联。这个过程我们称为注册路由 (route),路由负责管理 URL 和函数之间的映射,而这个函数则被称为视图函数 (view function)。route() 装饰器的第一个参数是 URL 规则,用字符串表示,必须以斜杠(/)开始。这里的 URL 是相对 URL(又称为内部 URL ),即不包含域名的 URL。

为视图函数绑定多个 URL

一个视图函数可以绑定多个 URL,比如下面的代码把 /hi 和 /hello 都绑定到 say_hello() 函数上,这就会为 say_hello 视图注册两个路由,用户访问这两个 URL 均会触发 say_hello() 函数,获得相同的响应。

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

为视图函数绑定动态 URL

在 URL 规则中添加变量部分,使用“”的形式表示。Flask 处理请求时会把变量传入视图函数,所以我们可以添加参数获取这个变量值。

@app.route('/greet/<name>')
def index(name):
    return "hello,{}".format(name)
@app.route('/greet/<name>')
def index(name):
    return "hello,{}".format(name)

因为 URL 中可以包含变量,所以我们将传入 app.route() 的字符串称为 URL 规则,而不是 URL。Flask 会解析请求并把请求的URL与视图函数的URL规则进行匹配。比如,这个 index 视图的 URL 规则为 '/greet/',那么类似 /greet/foo、/greet/bar 的请求都会触发这个视图函数。还可以设置默认值,这样即使不输入 name 的值访问也不会 404

启动开发服务器

Flask 内置了一个简单的开发服务器(由依赖包 Werkzeug 提供), 足够在开发和测试阶段使用。Flask 通过依赖包 Click 内置了一个 CLI(Command Line Interface,命令行交互界面)系统。当我们安装 Flask 后,会自动添加一个 flask 命令脚本,我们可以通过 flask 命令执行内置命令、扩展提供的命令或是我们自己定义的命令。其中,flask run 命令用来启动内置的开发服务器。

pipenv run flask run

自动发现程序

一般来说,在执行 flask run 命令运行程序前,我们需要提供程序实例所在模块的位置。我们在上面可以直接运行程序,是因为 Flask 会自动探测程序实例,自动探测存在下面这些规则:

  • 从当前目录寻找 app.pywsgi.py 模块,并从中寻找名为 app 或 application 的程序实例。
  • 从环境变量 FLASK_APP 对应的值寻找名为 app 或 application 的程序实例。

因为我们的程序主模块命名为 app.py,所以 flask run 命令会自动在其中寻找程序实例。如果你的程序主模块是其他名称,比如 hello.py,那么需要设置环境变量 FLASK_APP,将包含程序实例的模块名赋值给这个变量。Linux 或 macOS 系统使用 export 命令:

export FLASK_APP=hello

管理环境变量

Flask的自动发现程序实例机制还有第三条规则:如果安装了 python-dotenv,那么在使用 flask run 或其他命令时会使用它自动从 .flaskenv 文件和 .env 文件中加载环境变量。当安装了 python-dotenv 时,Flask 在加载环境变量的优先级是:
手动设置的环境变量>.env 中设置的环境变量>.flaskenv 设置的环境变量。
除了 FLASK_APP,在后面我们还会用到其他环境变量。环境变量在新创建命令行窗口或重启电脑后就清除了,每次都要重设变量有些麻烦。而且如果你同时开发多个 Flask 程序,这个 FLASK_APP 就需要在不同的值之间切换。为了避免频繁设置环境变量,我们可以使用 python-dotenv 管理项目的环境变量,首先使用 Pipenv 将它安装到虚拟环境:

pipenv install python-dotenv

我们在项目根目录下分别创建两个文件:.env 和 .flaskenv。.flaskenv 用来存储和 Flask 相关的公开环境变量,比如 FLASK_APP;而 .env 用来存储包含敏感信息的环境变量,比如后面我们会用来配置 Email 服务器的账户名与密码。在 .flaskenv 或 .env 文件中,环境变量使用键值对的形式定义,每行一个以#开头的为注释。.env 包含敏感信息,除非是私有项目,否则绝对不能提交到 Git 仓库中。当你开发一个新项目时,记得把它的名称添加到 .gitignore 文件中, 这会告诉 Git 忽略这个文件。gitignore 文件是一个名为 .gitignore 的文本文件,它存储了项目中 Git 提交时的忽略文件规则清单。

在上面启动的 Web 服务器默认是对外不可见的,可以在 run 命令后添加 --host 选项将主机地址设为 0.0.0.0 使其对外可见

flask run --host=0.0.0.0

内网穿透工具

  • ngrok
  • Localtunnel

改变默认端口

Flask 提供的 Web 服务器默认监听 5000 端口,你可以在启动时传入参数来改变它

flask run --port=8000

执行 flask run 命令时的 host 和 port 选项也可以通过环境变量 FLASK_RUN_HOSTFLASK_RUN_PORT 设置。事实上,Flask 内置的命令都可以使用这种模式定义默认选项值,即 “FLASK_<COMMAND>_<OPTION>”,你可以使用 flask--help 命令查看所有可用的命令。

设置运行环境

开发环境(development enviroment)和生产环境(production enviroment)是我们会频繁接触到的概念。
开发环境是指我们在本地编写和测试程序时的计算机环境,
而生产环境与开发环境相对,它指的是网站部署上线供用户访问时的服务器环境。
根据运行环境的不同,Flask 程序、扩展以及其他程序会改变相应的行为和设置。为了区分程序运行环境,Flask 提供了一个 FLASK_ENV 环境变量用来设置环境,默认为 production 。在开发时,我们可以将其设为 development ,这会开启所有支持开发的特性。为了方便管理,我们将把环境变量 FLASK_ENV 的值写入 .flaskenv 文件中:

FLASK_ENV=development

在开发环境下,调试模式(Debug Mode)将被开启,这时执行 flask run 启动程序会自动激活 Werkzeug 内置的调试器(debugger)和重载器(reloader),它们会为开发带来很大的帮助。如果你想单独控制调试模式的开关,可以通过 FLASK_DEBUG 环境变量设置,设为 1 则开启,设为 0 则关闭,不过通常不推荐手动设置这个值。
在生产环境中部署程序时,绝不能开启调试模式。尽管 PIN 码可以避免用户任意执行代码,提高攻击者利用调试器的难度,但并不能确保调试器完全安全,会带来巨大的安全隐患。而且攻击者可能会通过调试信息获取你的数据库结构等容易带来安全问题的信息。另一方面,调试界面显示的错误信息也会让普通用户感到困惑。
当在一个新电脑创建运行环境时,使用 pipenv install 命令时需要添加额外的 --dev 选项才会安装 dev-packages 部分定义的开发依赖包。

项目配置

在Flask中,配置变量就是一些大写形式的 Python 变量,你也可以称之为配置参数或配置键。使用统一的配置变量可以避免在程序中以硬编码(hard coded)的形式设置程序。在一个项目中,你会用到许多配置:Flask 提供的配置,扩展提供的配置,还有程序特定的配置。和平时使用变量不同,这些配置变量都通过 Flask 对象的 app.config 属性作为统一的接口来设置和获取,它指向的 Config 类实际上是字典的子类,所以你可以像操作其他字典一样操作它。

app.config['ADMIN_NAME']='Eric'

配置的名称必须是全大写形式,小写的变量将不会被读取。使用 update() 方法则可以一次加载多个值:

app.config.update(
    TESTING1=True
    TESTING2=flase
)

和操作字典一样,读取一个配置就是从config字典里通过将配置变量的名称作为键读取对应的值:

value = app.config['ADMIN_NAME']

URL与端点

在 Web 程序中,URL 无处不在。如果程序中的 URL 都是以硬编码的方式写出,那么将会大大降低代码的易用性。比如,当你修改了某个路由的URL 规则,那么程序里对应的 URL 都要一个一个进行修改。更好的解决办法是使用 Flask 提供的 url_for() 函数获取 URL,当路由中定义的 URL 规则被修改时,这个函数总会返回正确的 URL。调用 url_for() 函数时,第一个参数为端点(endpoint)值。在 Flask 中,端点用来标记一个视图函数以及对应的 URL 规则。

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

这个路由的端点即视图函数的名称 index,调用 url_for('index')即可获取对应的 URL,即“/”。
如果 URL 含有动态部分,那么我们需要在 url_for() 函数里传入相应的参数,以下面的视图函数为例:

@app.route('/hello/<name>')
def index(name):
    return "hello {}".format(name)

这时使用 url_for('say_hello',name='Jack')得到的 URL 为“/hello/Jack”。
我们使用 url_for()函数生成的 URL 是相对 URL(即内部URL), 即URL中的path部分,比如“/hello”,不包含根URL。相对URL只能在程序内部使用。如果你想要生成供外部使用的绝对 URL,可以在使用 url_for()函数时,将 _external 参数设为 True,这会生成完整的 URL, 比如 http://helloflask.com/hello,在本地运行程序时则会获得 http://localhost:5000/hello。

Flask 命令

除了 Flask 内置的 flask run 等命令,我们也可以自定义命令。在虚拟环境安装 Flask 后,包含许多内置命令的 flask 脚本就可以使用了。在前面我们已经接触了很多 flask 命令,比如运行服务器的 flask run,启动 shell 的 flask shell。通过创建任意一个函数,并为其添加 app.cli.command() 装饰器, 我们就可以注册一个 flask 命令。hello() 命令函数,在函数中我们仍然只是打印一行问候。

@app.cli.command() 
def hello():
    click.echo("hello,flask")

函数的名称即为命令名称,这里注册的命令即 hello,你可以使用 flask hello 命令来触发函数。作为替代,你也可以在 app.cli.command() 装饰器中传入参数来设置命令名称,比如 app.cli.command('hello'), 会把命令名称设置为hello,完整的命令即flask hello。