1.1 搭建开发环境
- 用pip+virtualenv管理包和虚拟环境
- 这里个人推荐使用conda环境和包管理工具(MiniConda)
- Pipenv
一个程序通常会使用很多的Python包,即依赖(dependency)。而程序不仅仅会在一台电脑上运行,程序部署上线时需要安装到远程服务 器上,而你也许会把它分享给朋友。如果你打算开源的话,就可能会有 更多的人需要在他们的电脑上运行。为了能顺利运行程序,他们不得不记下所有依赖包,然后使用pip、conda或Pipenv安装,这些重复无用的工作当然应该避免。在以前我们通常使用pip搭配一个requirements.txt文件来记录依赖。但requirements.txt需要手动维护,在使用上不够灵活。Pipfile的出现就是为了替代难于管理的requirements.txt。
安装flask,这些包管理工具同时也会安装flask的相关依赖包
1.2.1 创建程序实例
以下app.py脚本中包含一个最小的Flask程序
对于简单的的程序来说,程序的主模块一般命令为app.py。你也可以 使用其他名称,比如hello.py,但是要避免使用flask.py,因为这和Flask 本身冲突。
#app.py
from flask import Flask
app = Flask(__name__) #创建Flask程序实例
#Flask类是Flask的核心类,它提供了很多与程序相关的属性和方法。
@app.route('/')
def index():
return 'Hello Flask!
'
传入Flask类构造方法的第一个参数是模块或包的名称,我们应该使用特殊变量name。Python会根据所处的模块来赋予name变量相应的值,对于我们的程序来说(app.py),这个值为app。除此之外,这也会帮助Flask在相应的文件夹里找到需要的资源,比如模板和静态文件。
1.2.2 注册路由
在一个Web应用里,客户端和服务器上的Flask程序的交互可以简单 概括为以下几步:
1)用户在浏览器输入URL访问某个资源。
2)Flask接收用户请求并分析请求的URL。
3)为这个URL找到对应的处理函数。
4)执行函数并生成响应,返回给浏览器。
5)浏览器接收并解析响应,将信息显示在页面中。
上面这些步骤大部分都由Flask完成,我们要做的是建立处理请求的函数,并为其定义对应的URL规则。只需为函数附加 app.route()装饰器,并传入URL规则作为参数,我们就可以让URL与函数建立关联。这个过程我们称为注册路由(route),路由负责管理 URL和函数之间的映射,而这个函数则被称为视图函数(view function)。
@app.route('/')
def index():
return 'Hello, World!
'
在这个程序里,app.route()装饰器把根地址/和index()函数绑定起来,当用户访问这个URL时就会触发index()函数。这个视图函数可以像其他普通函数一样执行任意操作,比如从数据库中获取信息, 获取请求信息,对用户输入的数据进行计算和处理等。最后,视图函数 返回的值将作为响应的主体,一般来说,响应的主体就是呈现在浏览器窗口的HTML页面。
虽然这个程序相当简单,但它却是大部分Flask程序的基本模式。在复杂的程序中,我们会有许多个视图函数分别处理不同URL的请求,在视图函数中会完成更多的工作,并且返回包含各种链接、表单、图片的 HTML文件,而不仅仅是一行字符串。返回的页面中的链接又会指向其 他URL,被单击后触发对应的视图函数,获得不同的返回值,从而显示 不同的页面,这就是我们浏览网页时的体验。
route()装饰器的第一个参数是URL规则,用字符串表示,必须以 斜杠(/)开始。这里的URL是相对URL(又称为内部URL),即不包含域名的URL
1 为视图绑定多个URL:
一个视图函数可以绑定多个URL,比如下面的代码把/hi和/hello都 绑定到say_hello()函数上,这就会为say_hello视图注册两个路由,用 户访问这两个URL均会触发say_hello()函数,获得相同的响应
@app.route('/hi')
@app.route('/hello')
def say_hello():
return 'Hello, Flask!
'
**2 动态URL **
我们不仅可以为视图函数绑定多个URL,还可以在URL规则中添加变量部分,使用“<变量名>”的形式表示。Flask处理请求时会把变量传入视图函数,所以我们可以添加参数获取这个变量值
@app.route('/greet/')
def greet(name):
return 'Hello, %s!
' % name
《多个参数怎么操作呢???》
因为URL中可以包含变量,所以我们将传入app.route()的字符串称为URL规则而不是URL。Flask会解析请求并把请求的URL与视图函数的URL规则进行匹配。比如,这个greet视图的URL规则 为/greet/
当URL规则中包含变量时,如果用户访问的URL中没有添加变量, 比如/greet,那么Flask在匹配失败后会返回一个404错误响应。一个很常见的行为是在app.route()装饰器里使用defaults参数设置URL变量的默 认值,这个参数接收字典作为输入,存储URL变量和默认值的映射。在下面的代码中,我们为greet视图新添加了一个app.route()装饰器,
@app.route('/greet', defaults={'name': 'Programmer'})
@app.route('/greet/')
def greet(name):
return 'Hello, %s!
' % name
这时如果用户访问/greet,那么变量name会使用默认值 Programmer,视图函数返回
Hello,Programmer!
。上面的用 法实际效果等同于:@app.route('/greet')
@app.route('/greet/')
def greet(name='Programmer'):
return 'Hello, %s!
' % name
1.3 启动开发服务器
Flask内置了一个简单的开发服务器(由依赖包Werkzeug提供), 足够在开发和测试阶段使用。在生产环境需要使用性能够好的生产服务器,以提升安全和性能。
Flask通过依赖包Click内置了一个CLI(Command Line Interface,命 令行交互界面)系统。当我们安装Flask后,会自动添加一个flask命令脚本,我们可以通过flask命令执行内置命令、扩展提供的命令或是我们自己定义的命令。其中,flask run命令用来启动内置的开发服务器。启动时确保执行命令前激活了虚拟环境。
flask run命令运行的开发服务器默认会监听http://127.0.0.1:5000/地址(按Crtl+C退出),并开启多线程支持。http://localhost::5000/与http://127.0.0.1:5000/除了地址不同外,两者没有实际区别,即域名和IP地址的映射关系。
旧的启动开发服务器的方式是使用app.run()方法,目前已不推荐 使用(deprecated)。
一般来说,在执行flask run命令运行程序前,我们需要提供程序实例所在模块的位置。我们在上面可以直接运行程序,是因为Flask会自动 探测程序实例,自动探测存在下面这些规则:
·从当前目录寻找app.py和wsgi.py模块,并从中寻找名为app或application的程序实例。
·从环境变量FLASK_APP对应的值寻找名为app或application的程序实例。
因为我们的程序主模块命名为app.py,所以flask run命令会自动在 其中寻找程序实例。如果你的程序主模块是其他名称,比如hello.py, 那么需要设置环境变量FLASK_APP,将包含程序实例的模块名赋值给这个变量。
#Linux或macOS系统使用export命令
export FLASK_APP=hello
#在Windows系统中使用set命令:
set FLASK_APP=hello
我们在上面启动的Web服务器默认是对外不可见的,可以在run命 令后添加--host选项将主机地址设为0.0.0.0使其对外可见:
flask run --host=0.0.0.0
这会让服务器监听所有外部请求。个人计算机(主机)一般没有公 网IP(公有地址),所以你的程序只能被局域网内的其他用户通过你的 个人计算机的内网IP(私有地址)访问,比如你的内网IP为 192.168.191.1。当局域网内的其他用户访问http://192.168.191.1:5000时, 也会看到浏览器里显示一行“Hello,Flask!”。要让互联网上的所有人都可访问,可以考虑使用ngrok(https://ngrok.com/)Localtunnel(https://localtunnel.github.io/www/)等内网穿透/端口转发工 具。
Flask提供的Web服务器默认监听5000端口,你可以在启动时传入参数来改变它:
flask run --port=8000
执行flask run命令时的host和port选项也可以通过环境变量 FLASK_RUN_HOST和FLASK_RUN_PORT设置。事实上,Flask内置的
命令都可以使用这种模式定义默认选项值, 即“FLASK_
** 设置运行环境**
开发环境是指我们在本地编写和测试程序时的计算机环境,而生产环境与开发环境相对,它指的是网站部署上线供用户访问时的服务器环境。
根据运行环境的不同,Flask程序、扩展以及其他程序会改变相应的行为和设置。为了区分程序运行环境,Flask提供了一个FLASK_ENV环境变量用来设置环境,默认为production(生产)。在开发时,我们可 以将其设为development(开发),这会开启所有支持开发的特性
在开发环境下,调试模式(Debug Mode)将被开启,这时执行flask run启动程序会自动激活Werkzeug内置的调试器(debugger)和重载器 (reloader),它们会为开发带来很大的帮助。如果你想单独控制调试模式的开关,可以通过FLASK_DEBUG环境 变量设置,设为1则开启,设为0则关闭,不过通常不推荐手动设置这个
值。
在生产环境中部署程序时,绝不能开启调试模式。尽管PIN码可以 避免用户任意执行代码,提高攻击者利用调试器的难度,但并不能确保 调试器完全安全,会带来巨大的安全隐患。而且攻击者可能会通过调试 信息获取你的数据库结构等容易带来安全问题的信息。另一方面,调试 界面显示的错误信息也会让普通用户感到困惑。
1.调试器
Werkzeug提供的调试器非常强大,当程序出错时,我们可以在网页 上看到详细的错误追踪信息,这在调试错误时非常有用。
2.重载器
当我们对代码做了修改后,期望的行为是这些改动立刻作用到程序 上。重载器的作用就是监测文件变动,然后重新启动开发服务器。默认会使用Werkzeug内置的stat重载器,它的缺点是耗电较严重, 而且准确性一般。为了获得更优秀的体验,我们可以安装另一个用于监 测文件变动的Python库Watchdog,安装后Werkzeug会自动使用它来监测 文件变动
此外如果项目中使用了单独的CSS或JavaScript文件时,那么浏览器可能会缓存这些文件,从而导致对文件做出的修改不能立刻生效。在浏览器中,我们可以按下Crtl+F5或Shift+F5执行硬重载(hard reload),即忽略缓存并重载(刷新)页面。
1.5 Flask扩展
我们将会接触到很多Flask扩展。扩展(extension)即使用 Flask提供的API接口编写的Python库,可以为Flask程序添加各种各样的功能。大部分Flask扩展用来集成其他库,作为Flask和其他库之间的薄薄一层胶水。因为Flask扩展的编写有一些约定,所以初始化的过程大致相似。大部分扩展都会提供一个扩展类,实例化这个类,并传入我们创建的程序实例app作为参数,即可完成初始化过程。通常,扩展会在传 入的程序实例上注册一些处理函数,并加载一些配置。
在日常开发中,大多数情况下,我们没有必要重复制造轮子,所以选用扩展可以避免让项目变得臃肿和复杂。尽管使用扩展可以简化操作,快速集成某个功能,但同时也会降低灵活性。如果过度使用扩展, 在不需要的地方引入,那么相应也会导致代码不容易维护。更糟糕的是,质量差的扩展可能还会带来潜在的Bug,而不同扩展之间也可能会出现冲突。因此,在编写程序时,应该尽量从实际需求出发,只在需要的时候使用扩展,并把扩展的质量和兼容性作为考虑因素,尽量在效率和灵活性之间达到平衡。
1.6 项目配置
在很多情况下,你需要设置程序的某些行为,这时你就需要使用配置变量。在Flask中,配置变量就是一些大写形式的Python变量,你也可 以称之为配置参数或配置键。使用统一的配置变量可以避免在程序中以硬编码(hard coded)的形式设置程序。
在一个项目中,你会用到许多配置:Flask提供的配置,扩展提供的配置,还有程序特定的配置。和平时使用变量不同,这些配置变量都通过Flask对象的app.config属性作为统一的接口来设置和获取,它指向的 Config类实际上是字典的子类,所以你可以像操作其他字典一样操作 它。
Flask提供了很多种方式来加载配置。比如,你可以像在字典中添加 一个键值对一样来设置一个配置:
app.config['ADMIN_NAME'] = 'Peter'
#配置的名称必须是全大写形式,小写的变量将不会被读取。
使用update()方法则可以一次加载多个值:
app.config.update( TESTING=True,
SECRET_KEY='_5#yF4Q8z\n\xec]/' )
除此之外,你还可以把配置变量存储在单独的Python脚本、JSON 格式的文件或是Python类中,config对象提供了相应的方法来导入配 置,具体我们会在后面了解。
#和操作字典一样,读取一个配置就是从config字典里通过将配置变 量的名称作为键读取对应的值:
value = app.config['ADMIN_NAME']
1.7 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/')
def greet(name):
return 'Hello %s!' % 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://localhost:5000/hello。
1.8 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, Human!')
函数的名称即为命令名称,这里注册的命令即hello,你可以使用 flask hello命令来触发函数。作为替代,你也可以在app.cli.command() 装饰器中传入参数来设置命令名称,比如app.cli.command('say-hello') 会把命令名称设置为say-hello,完整的命令即flask say-hello。
1.9 模板与静态文件
一个完整的网站需要使用模板(template)和静态文件(static file)来生成内容丰富的网页。 模板即包含程序页面的HTML文件,静态文件则是需要在HTML文件中加载的CSS、JavaScript文件,以及图片、字体文件等资源文件。默认情况下,模板文件存放在项目根目录中的templates文件夹中,静态文件存放在static文件夹下,这两个文件夹需要和包含程序实例的模块处于同一个目录下,对应的项目结构示例如下所示:
hello/
- templates/
- static/
- app.py
在开发Flask程序时,使用CSS框架和JavaScript库是很常见的需求,一般会直接从CDN加载资源或者下载至本地作为本地资源使用。建议在开发环境下自己下载到static目录下使用本地资源,这样可以提高加载速度,方便统一管理,出于方便的考虑也可以使用扩展内置的本地资源。在过渡到生产环境时,自己手动管理所有本地资源或自己设置CDN,避免使用扩展内置的资源。这个建议主要基于如下考虑因素:
·鉴于国内的网络状况,扩展默认使用的国外CDN可能会无法访问或访问过慢。
·不同扩展内置的加载方法可能会加载重复的依赖资源,比如 jQuery。
·在生产环境下,将静态文件集中在一起更方便管理。
·扩展内置的资源可能会出现版本过旧的情况。
·也存在资源链接失效的情况
CDN指分布式服务器系统。服务商把你需要的资源存储在分布于不 同地理位置的多个服务器,它会根据用户的地理位置来就近分配服务器 提供服务(服务器越近,资源传送就越快)。使用CDN服务可以加快网页资源的加载速度,从而优化用户体验。对于开源的CSS和JavaScript 库,CDN提供商通常会免费提供服务。
1.10 Flask与MVC架构
MVC架构最初是用来设计桌面程序的,后来也被用于Web程序,应 用了这种架构的Web框架有Django、Ruby on Rails等。在MVC架构中, 程序被分为三个组件:数据处理(Model)、用户界面(View)、交互逻辑(Controller)。如果套用MVC架构的内容,那么Flask中视图函数的名称其实并不严谨,使用控制器函数(Controller Function)似乎更合适些,虽然它也附带处理用户界面。严格来说,Flask并不是MVC架构的框架,因为它没有内置数据模型支持。
粗略归类,如果想要使用Flask来编写一个MVC架构的程序,那么视图函数可以作为控制器(Controller),视图(View)则是使用Jinja2渲染的HTML模板,而模型(Model)可以使用其他库来实现,比如SQLAlchemy来创建数据库模型。