这几天想学新东西,就看了flask框架,本身对python不太了解,网上的很多教程看了,总是在某些地方卡住。翻到一本电子书《Flask web Development》,还不错。但是说实话,英语比较渣,原文看起来很累,并且由于偷懒总习惯只看代码,而不看作者的解释。这样学习没有效率,第一遍很快只看代码就完成了,程序也能跑起来。但只图快的后果就是几乎没啥收获,很多东西都只知其然。
为了避免偷懒,下决心每天翻译一点,坚持仔细认真学习。从第二章开始,第一张的安装神马的都跳过了。翻译尽量卡着原字句走,只根据汉语习惯略作调整;有些专用语可能不准确,另外尽量不看字典,只凭上下文猜不认识的单词——据说这也是背单词手段之一:-)。所以必然会有错误之处,欢迎指正。
第二章 基本程序结构
本章你将学到Flask程序的不同部分,你也将编写并运行你的第一个Flask应用。
初始化
所有的Flask程序必须创建一个"程序实例",Web服务器将自己接收到的所有客户端请求转发给这一对象,使用的是网页服务网管接口协议(WSGI)。这一程序实例是一个Flask类的对象,通常通过如下代码进行创建:
from flask import Flask
app = Flask(__name__)
Flask类初始化参数是程序的包或主模块的名字。对大多数程序来说,Python的__name__
变量就是正确的值。
注意:
传递给Flask程序构造函数的name参数往往给新开发人员造成困扰。Flask采用这个参数判定程序的根目录,以便于以后在程序中引用和查找相关资源。
稍后,你将看到更多完整的程序初始化的例子,单对一个简单程序来说已经足够了。
路由和视图函数
像浏览器这样的客户端发送请求(request)给服务器,服务器按顺序转发给Flask程序实例。程序实例需要知道对每个URL(统一资源定位符,也就是你在地址栏里看到的那一大长串)
请求运行哪一部分的代码,所以它保持着一个关于URL和Python函数的映射表。对URL和函数之间关联进行控制的就称之为路由(route)。
定义路由的最方便的方法是通过app.route装饰器暴露|装饰一个函数,程序实例就会把这一函数注册为一个路由。下列代码展示了如何使用装饰器来声明一个路由:
@app.route('/')
def index():
return '<h1>Hello World!</h1>`
注意:
*装饰器(Decorator)是python语言的标准功能,它可以在不同方式下更改一个函数的行为。最常见的就是使用装饰器指定一个函数作为某事件的处理器 *
上述示例代码将index()函数注册为程序根URL的处理控制器。在程序部署到服务器上并关联www.example.com域名后,在浏览器访问www.example.com时,将触发程序的index()函数。该函数的返回值——称之为响应(response),就是客户端接收到的内容。如果客户端是浏览器,该响应就是展示用网页形式给用户的文档。
像index()这样的函数被称为视图函数(view functions)。视图函数的返回响应可以是简单的html文本串,也可以是包含更复杂的表单——稍后可见。
注意:
响应字符串嵌入在Python代码中将导致代码难以维护。这里只是简单介绍响应方式。你可以在第三章中学到如何正确生成响应的方法。
如果注意下每天使用网络服务的URL的形式,你可能会看到包含变量段。举例来说:你的facebook个人信息页面地址应该是http://www.facebook.com/<yourname>
,你的名字就是变量部分。Flask通过路由装饰器的特殊语法来支持这一类型的URL。下面的例子定义了一个拥有变量名称的路由:
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
小括号部分就是动态部分,符合静态部分的URL请求会被映射到这一路由。调用视图函数时,Flask就会把动态部分作为一个参数发给它。在上面的视图函数里,这一参数被用来生成个性化的欢迎信息并作为一个响应返回。
在路由中动态部分默认是作为字符串处理,但也可以指定为某种类型。如:/user/<int:id>
将只匹配包含整数的id动态参数URL。Flask路由支持int,float和path。path类型看起来也是一个字符串但其中的斜杠并不被认为是分隔符,而是动态参数的一部分,不再对其进行解释。
启动服务
程序实例有run方法来运行Flask集成开发服务器:
if __name__ == '__main__':
app.run(debug=True)
__name__=='__main__'
是python常见的方言,这里是用来保证仅仅在直接运行脚本的情况下启动开发服务器。当脚本被其他脚本导入时,它将假定调用它的父级脚本会运行另外的服务器,就跳过后面app.run()
。
一旦服务器启动,它就进入循环,等待请求并为之提供服务。该循环持续不断直到程序结束,例如按下Ctrl+C。
还有基本参数选项可以指定给app.run()
来配置服务器操作模式。在开发模式下,服务器将启动调试,这将激活debugger和reloader(调试模式和重载)
。你只需设置参数为debug=True
注意:
Flask提供的web服务器"不可用于生产环境"。你将在第七章学习如何设置生产web服务器。
完整的程序
在上一节,你已经学习了Flask的不同部分,现在是时候自己写一个了。完整的hello.py脚本程序仅仅就是将我们在前面描述过的三部分内容合并到一个文件中,如下:
Example 2-1. hello.py: A complete Flask application
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True)`
要运行这个程序,你需要确保先前你设置的虚拟开发环境(第一章设置 安装python 后 pip install virtualenv)
已激活并且安装了Flask。现在打开你的web浏览器并在地址栏里输入http://127.0.0.1:5000/
,如果正常的话,浏览器将显示服务器的响应——页面出现“Hello World!”。当然,运行服务器的话需要在命令行中输入如下命令:
(venv)$python hello.py
*running on http://127.0.0.1:5000
*Restarting with realoader`
如果你输了其他的URL,该程序就不知道应该如何处理,它将返回一个404错误代码给浏览器——这意味着你要访问的页面并不存在。
改进版本的程序如2-2示例所示,它添加了第二路由——包含动态参数。当你访问该URL时,将见到个性化的欢迎。
Example 2-2. hello.py: Flask application with a dynamic route
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)`
要测试该动态路由,启动服务器后访问http://localhost:5000/user/Dave
,程序将以个性化的欢迎来响应,根据动态参数name生成欢迎信息。每当尝试不同的名字,你会看到根据名字生成欢迎页面。
译注:2016-7-20,现在全新安装虚拟环境的话,Flask已经升级到0.11.1了,会出现一些提醒信息,比如导入扩展的语法将要从
form flask.ext.moment import Moment
变更为from flask_moment import Moment
。当然,这并不意味着是错误,程序仍旧可以照常运行的。这是为将来大版本语法变动的预防针:-)
请求-响应循环
现在你已经搞定一个基本的Flask程序了,你可能希望对Flask工作过程了解的更多。下面的小节描述了一些框架的设计模样(?)。
应用程序和请求上下文
当Falsk接收到客户端的请求,他需要激活一些视图函数需要的对象来处理它们。最好的例子就是request对象,它封装了所有的客户端http请求。
最简单的作法是将它作为一个参数发送,这样Flask就能让一个视图函数访问“请求对象”,但这也会导致每个单一视图函数都要有可扩展的参数:视图函数要完成一个请求访问,可能需要访问不仅是请求对象自己。考虑到这些,情况就更复杂了。
为了避免杂乱的视图函数带着不一定需要的大量参数,Flask使用上下文(contexts),来暂时为特定对象提供被全局访问的可能。多亏了上下文,视图函数就可以如下书写:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your browser is %s</p>' % user_agent
注意,在这个视图函数中request就像一个全局变量一样被使用。实际上,你仔细考虑下就会发现,request不可能是一个全局变量——在一个多线程服务器上,在同一时间线程工作在不同客户端请求上,所以每个线程需要看到不同的——自己特定的那个request对象。上下文就使Flask的这一功能实现,某一特定变量只对一个线程可见而其他的则看不到。
注意:
线程 是可以独立管理的最小指令序列。通常,一个进程(process)拥有多个活动的线程,他们可以共享资源如内存或文件句柄。多线程web服务器会启动一个线程池,每当进来一个请求就从线程池里调起一个线程来进行处理。
在Flask中有两个上下文:程序上下文和请求上下文。下表展示了每个上下文拥有的变量。
*变量名* *上下文* *描述*
current_app 程序上下文 当前活动的程序实例
g 程序上下文 对象程序可以请求发起是用来临时存储数据,每个请求发起都会重置。
request 请求上下文 请求对象本身,封装了客户端发来的http请求内容
session 请求上下文 用户会话,一个字典,程序可以用来在各个请求之间记住存储的值。
在分发请求或处理后移除请求前,flask激活(或推出)程序上下文和请求上下文。当程序上下文被推出,current_app和g 变量对线程来说就生效了,同样,当请求上下文被推出,request和session也将生效。如果激活没有活动程序或请求上下文就访问这些变量,将导致报错。如果你还不太理解他们为何这么有用,不需要担心,后面我们将详细了解
下列python shell会话模拟展示了程序上下文是如何工作的:
>>> from hello import app
>>> from flask import current_app
>>> current_app.name
Traceback (most recent call last):
...
RuntimeError: working outside of application context
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()
在这里,如果没有程序激活则current_app.name失败了,相反上下文被推出则有效。注意,在程序实例上是如何通过app.app_context()来调用程序上下文的。
请求分发
当程序接收到客户端请求时,它需要查找视图函数来为之服务。在这任务里,Flask在URL映射中(包含着URL和对应视图函数的映射)
查找请求的URL来处理之。Flask通过app.route装饰器(或等价的非装饰器版本:app.add_url_rule()函数)
来创建这一映射。
Url映射是啥样的呢,你可以检查在python shell 中hello.py创建的这个映射。首先确保你的虚拟安装环境激活:
(venv) $ python
>>> from hello import app
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
‘/’和/user/<name>路由是在程序中通过app.route装饰器定义的。/static/<filename>路由则是Flask添加用来访问静态文件的。你将在第三张学到更多的关于静态文件的东西。
在URL映射中出现的HEAD,OPTIONS,GET元素是路由控制处理的“请求方法”。Flask给每个路由附加上方法,这样不同的请求方法就会被发送到同一URL但被不同的视图函数处理。HEAD和OPTIONS方法由Flask自动管理,所以实际上也可以说本程序的三个路由在URL映射中添加了GET方法。
在第四章你将学习路由不同请求之间的差异。
请求钩子
有些时候,需要在请求被执行之前或之后执行某些代码,钩子就派上了用场。举例来说,在每个请求的开始部分,可能需要创建一个数据库连接,或者许可用户执行该请求。Flask提供了选项来注册一个通用函数,以便于在一个请求被分派到视图函数之前或之后来调用,这可以避免在每个视图函数中重复复制代码来实现其功能。
请求钩子以装饰器的形式来实现。Flask支持四种钩子:
before_first_request: 在处理第一个请求之前执行该注册函数
before_request: 在每个请求前都执行该注册函数
after_request: 在每个请求之后都执行该注册函数,如果没有未被处理的错误的话。
teardown_request: 在每个请求之后都执行该注册函数,即使有未被处理的错误
为了在请求钩子和视图函数之间共享数据,一个常用方案就是使用全局上下文g。举例来说,before_request句柄能从数据库中加载已登录的用户,并将之存储在g.user对象。然后,如果调用视图函数,他就可以从这访问用户信息。
在下面章节中将展示请求钩子,所以即使现在没有啥感觉也不需担心。
响应
当Flask调用视图函数,它期待该函数将返回值作为对请求的回应。在大部分情况下,响应是一个简单的字符串被发送给客户端呈现为HTML页面。
但HTTP协议要求不仅仅是字符串作为响应。HTTP响应一个非常重要的部分就是响应状态代码,flask通常默认设置为“200”,它代表请求被成功回复。
当视图函数需要用不同的状态代码回复响应时,它就会把一个数值添加在相应文本后,作为第二个返回值。举例来说,下列视图函数将返回状态码400,它意味着请求错误:
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
视图函数返回的响应也可以携带第三个参数——添加在一个http响应上的字典头部。这是比较少见的,但你将在第14章看到示例。
作为返回一个、两个、甚至三个值的元组的替代方案,Flask视图函数还可以选择返回一个对象(response对象)。make_response()函数可以取1、2或3个参数,这些值被作为视图函数的响应统一作为一个对象response返回。有时候,这很有用,他可以在视图函数内部进行转换然后使用响应对象的方法深度配置响应。下面是一个创建响应对象并在其中设置cookie的例子:
from flask import make_response
@app.route('/')
def index():
response = make_response('<h1>This document carries a cookie!</h1>')
response.set_cookie('answer', '42')
return response
这里出现了一个特殊响应类型叫redirect。这个响应并不包含页面文档,它只是给浏览器一个新的URL地址以供浏览器加载新页面。在第四章你将学到重定向通常和web表单同用。
一个重定向通常以302响应代码加上location头部的URL地址来声明。重定向响应可以使用三个返回值或者一个response对象来生成,但为了便于使用,Flask提供了redirect()函数住手来创建。
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
其他的特殊响应是abort函数,他被用来处理错误。下面的代码展示了当传递给URL的id参数没有匹配的用户是返回一个404状态码:
from flask import abort
@app.route('/user/<id>')
def get_user(id):
user = load_user(id)
if not user:
abort(404)
return '<h1>Hello, %s</h1>' % user.name
注意:abort并不是将控制交还给调用它的函数,而是以跳出一个错误的方式将控制转交给了web服务器。
Flask扩展
Flask本身被设计成可扩展的。他特意空出一些重要部分,诸如数据库,用户认证,这样便于你自由选择最适合那你程序的包或者自行编写。
社区创建了针对各种要求的多种扩展,如果不够还是有任意的标准python包或库可供选择使用。为了展示如何在程序中使用扩展,下面小节在你hello.py中添加了一个扩展以使程序能够处理命令行参数。
通过Flask-script实现命令行选项
Flask的开发服务器支持启动配置项,但唯一的方法就是在教程中给app.run()传递指定的参数。这非常不便,理想方法是公共命令行参数传递配置项。
Flask-Script就是这样一个给你的Flask程序添加命令行参数的扩展。他支持通常选项配置也支持自定义命令。
这个扩展可以通过pip安装:
(venv)$pip install flask-script
2-3例子展示了在hello.py中添加命令行解释器所需要进行的改动。
example2-3: hello.py:使用Flask-script
from flask.ext.script import Manager
manager = Manager(app)
# ...
if __name__ == '__main__':
manager.run()
扩展是Flask特殊种类在flask.exe命名空间下暴露。Flask-script输出一个名为Manager的类,可以从flask.ext.script中导入。
这个扩展的初始化方法和其他扩展类似:就爱能够应用程序示例作为参数传递给它的构造函数,从而生成一个类实例。该对象可以随后被其他扩展使用。在本例中,通过manager.run()路由进行服务器启动,同样可以由此分析解释命令行。
通过这些修改,程序要求一个基本的命令行选项配置参数。现在运行一下hello.py看看其可用的信息:
$ python hello.py
usage: hello.py [-h] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()
optional arguments:
-h, --help show this help message and exit`
shell命令可以用来在程序上下文中启动一个python shell 会话。你可以使用这个会话进行维护任务或者测试,调试问题。
runserver命令,就像其字面意思一样,将启动服务器:运行python hello.py runserver将 以调试模式启动服务器,同样,这里还有更多地可以选项:
(venv) $ python hello.py runserver --help
usage: hello.py runserver [-h] [-t HOST] [-p PORT] [--threaded]
[--processes PROCESSES] [--passthrough-errors] [-d]
[-r]
Runs the Flask development server i.e. app.run()
optional arguments:
-h, --help show this help message and exit
-t HOST, --host HOST
-p PORT, --port PORT
--threaded
--processes PROCESSES
--passthrough-errors
-d, --no-debug
-r, --no-reload`
--host 参数 非常有用,他告诉服务器监听哪个网络接口来连接客户端。默认情况,Flask开发服务器监听localhost的连接,这时候只有来自于本机内部的连接才会被接受。下列命令指定服务器鉴定在公用网络接口上的连接,允许网络中的其他计算机连接上来:
(venv) $ python hello.py runserver --host 0.0.0.0
* Running on http://0.0.0.0:5000/
* Restarting with reloader
web服务器现在允许网络中的任意计算机访问http://a.b.c.d:5000
, a.b.c.d是运行服务器计算机的扩展ip地址。
本章介绍了请求应答得概念,但关于应答内容很多并没有细谈。Flask根据模板对生成相应提供了很好的支持,他们非常重要我们将在后续章节中详解。
第三章 模板