欢迎光临!
若无相欠,怎会相见

源码阅读 – Flask v0.1 [01] 阅读准备

序言

两个月没写作业,有点儿慌,虽然每天都有笔记记录下来,但是还是在 3 月的最后几个小时写上 3 月的作业,哈哈

源码阅读环境

源仓库: https://github.com/Deteriorator/study-documents/blob/master/Read-Code/flask/flask-0.1,本文大量文字来自于 《Flask Web开发实战:入门、进阶与原理解析(李辉著 )》,

下载源码

首先从 github 上 clone 源码仓库,将代码下载到本地 :

git clone https://github.com/pallets/flask.git
git checkout 0.1

Python 环境

我一般使用 Anaconda 进行 Python 虚拟环境管理 , 同时 flask 的早期版本需要使用 Python 2.x 的环境,因此使用 Anaconda 创建一个 Python 2.7 环境 , 然后激活该环境 , 并在命令行中执行 :

pip install -i https://pypi.douban.com/simple -r requirements.txt

requirements.txt 的内容如下, flask 0.1 版本依赖这两个模块 。:

Werkzeug==0.6.1
Jinja2==2.4

源码阅读准备

阅读 flask 0.1 版本源码需要了解一些知识,下面写一下

Flask 的设计理念

“微”框架

在官方介绍中 , Flask 被称为微框架 , 这里的 “微” 并不意味着 Flask 功能简陋 , 而是指其保留核心且易于扩展 。 有许多 Web 程序不需要后台管理 、 用户认证 、 权限管理 , 有些甚至不需要表单或数据库 , 所以 Flask 并没有内置这类功能 , 而是把这些功能都交给扩展或用户自己实现 。 正因为如此 , 从只需要渲染模板的小项目 , 到需要各种功能的大项目 , Flask 几乎能够适应各种情况 。 Flask 的这一设计理念正印证了 《Zen of Python》 里的这一句 :

“Simple is better than complex.”

两个核心依赖

虽然 Flask 保持简单的核心 , 但它主要依赖两个库 —— Werkzeug 和 Jinja 。 Python Web 框架都需要处理 WSGI 交互 , 而 Werkzeug 本身就是一个非常优秀的 WSGI 工具库 , 几乎没有理由不使用它 , Flask 与 Werkzeug 的联系非常紧密 。 从路由处理 , 到请求解析 , 再到响应的封装 , 以及上下文和各种数据结构都离不开 Werkzeug , 有些函数 (比如 redirect 、 abort) 甚至是直接从 Werkzeug 引入的 。 如果要深入了解 Flask 的实现原理 , 必然躲不开 Werkzeug 。

引入 Jinja2 主要是因为大多数 Web 程序都需要渲染模板 , 与 Jinja2 集成可以减少大量的工作 。 除此之外 , Flask 扩展常常需要处理模板 , 而集成 Jinja2 方便了扩展的开发 。 不过 , Flask 并不限制你选择其他模板引擎 , 比如 Mako (http://www.makotemplates.org/) 、 Genshi(http://genshi.edgewall.org/)等 。

显式程序对象

在一些 Python Web 框架中 , 一个视图函数可能类似这样 :

from example_framework import route

@route('/')
def index():
    return 'Hello World!'

而在 Flask 中,则需要这样 :

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

应该看到其中的区别了 , Flask 中存在一个显式的程序对象 , 我们需要在全局空间中创建它 。 这样设计主要有下面几个原因 :

  • 前一种方式(隐式程序对象)在同一时间内只能有一个实例存在 , 而显式的程序对象允许多个程序实例存在 。
  • 允许你通过子类化 Flask 类来改变程序行为 。
  • Flask 需要通过传入的包名称来定位资源(模板和静态文件) 。
  • 允许通过工厂函数来创建程序实例 , 可以在不同的地方传入不同的配置来创建不同的程序实例。
  • 允许通过蓝本来模块化程序。

另外 , 这个设计也印证了 《Zen of Python》 里的这一条 : “Explicit is better than implicit.”

本地上下文

在多线程环境下 , 要想让所有视图函数都获取请求对象 。 最直接的方法就是在调用视图函数时将所有需要的数据作为参数传递进去 , 但这样一来程序逻辑就变得冗余且不易于维护 。 另一种方法是将这些数据设为全局变量 , 但是如果直接将请求对象设为全局变量 , 那么必然会在不同的线程中导致混乱 (非线程安全) 。 本地线程 (thread locals) 的出现解决了这些问题 。

本地线程就是一个全局对象 , 你可以使用一种特定线程且线程安全的方式来存储和获取数据 。 也就是说 , 同一个变量在不同的线程内拥有各自的值 , 互不干扰 。 实现原理其实很简单 , 就是根据线程的ID来存取数据 。 Flask 没有使用标准库的 threading.local() , 而是使用了 Werkzeug 自己实现的本地线程对象 werkzeug.local.Local() , 后者增加了对 Greenlet 的优先支持 。

Flask 使用本地线程来让上下文代理对象全局可访问 , 比如 request 、 session 、 current_app 、 g , 这些对象被称为本地上下文对象 (context locals) 。 因此 , 在不基于线程 、 greenlet 或单进程实现的并发服务器上 , 这些代理对象将无法正常工作 , 但好在仅有少部分服务器不被支持 。 Flask 的设计初衷是为了让传统 Web 程序的开发更加简单和迅速 , 而不是用来开发大型程序或异步服务器的 。 但是 Flask 的可扩展性却提供了无限的可能性 , 除了使用扩展 , 我们还可以子类化 Flask 类 , 或是为程序添加中间件 。

在 Flask 中存在三种状态 , 分别是程序设置状态 (application setup state) 、 程序运行状态 (application runtime state) 和请求运行状态 (request runtime state) 。

在 Flask 0.1 代码中 , 本地上下文信息如下 :

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

这里的 lambda 匿名函数可以视为如下函数 , 以 lambda: _request_ctx_stack.top.app 为例 :

def a():
    return _request_ctx_stack.top.app
>>> from flask import Flask, current_app, g, request, session
>>> app = Flask(__name__)
>>> current_app, g, request, session
(<LocalProxy unbound>,
<LocalProxy unbound>,
<LocalProxy unbound>,
<LocalProxy unbound>)

上述代码为书中的代码 , 我用 0.1 版的代码无法使用 , 实际为 :

>>> from flask import Flask, current_app, g, request, session
>>> app = Flask(__name__)
>>> current_app, g, request, session
(Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Anaconda3\envs\source\lib\site-packages\werkzeug\local.py", line 321, in __repr__
    obj = self._get_current_object()
File "D:\Anaconda3\envs\source\lib\site-packages\werkzeug\local.py", line 306, in _get_current_object
    return self.__local()
File "flask.py", line 660, in <lambda>
    current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
AttributeError: 'NoneType' object has no attribute 'app'

而我在实际中并没有成功以 0.1 版的代码进入到三种状态 , 因此我只以我的实际情况进行记录 。 如下 :

>>> from flask import Flask, current_app, g, request, session, _request_ctx_stack
>>> app = Flask(__name__)
>>> ctx = app.test_request_context()
>>> ctx.__enter__()
>>> ctx
<flask._RequestContext object at 0x0000000002C08470>
>>> current_app
<flask.Flask object at 0x0000000002C19358>
>>> request
<Request 'http://localhost/' [GET]>
>>> session
None
>>> g
<flask._RequestGlobals object at 0x000000000378E128>
>>> _request_ctx_stack     # 本地上下文堆栈
<werkzeug.local.LocalStack object at 0x0000000003779048>
>>> _request_ctx_stack._local.__storage__   #
{18532: {'stack': [<flask._RequestContext object at 0x0000000002C08470>]}}
>>>
>>> _request_ctx_stack.top
<flask._RequestContext object at 0x0000000002C08470>
>>> _request_ctx_stack.top.__dict__
{'g': <flask._RequestGlobals object at 0x000000000378E128>, 'url_adapter': <werkzeug.routing.MapAdapter object at 0x000000000377EB70>, 'app': <flask.Flask object at 0x0000000002C19358>, 'request': <Request 'http://localhost/' [GET]>, 'session': None, 'flashes': None}

从上述代码交互中可以看到 ‘g’ 就是全局变量 , app 是当前的 Flask 对象 , request 是当前的链接 , session 为空 。 由于这部分与 wsgi 的 werkzeug 相关 , 只能先放下 。 大概了解了 _request_ctx_stack , current_app , request , session 和 g 的数据结构 , 那么就接着阅读源代码 。 当然有个前提是先了解一下 wsgi 。

丰富的自定义支持

Flask 的灵活不仅体现在易于扩展 , 不限制项目结构 , 也体现在其内部的高度可定制化 。 比如 , 我们可以子类化用于创建程序实例的 Flask 类 , 来改变特定的行为 :

from flask import Flask
class MyFlask(Flask)
    pass
app = MyFlask(__name__)
...

除了 Flask 类 , 还可以自定义请求类和响应类 。 最常用的方式是子类化 Flask 内置的请求类和响应类 , 然后改变一些默认的属性 。 Flask 内部在使用这些类时并不直接写死 , 而是使用了定义在 Flask 属性上的中间变量 , 比如请求类存储在 Flask.request_class 中 。 如果要使用自己的请求类 , 那么只需要把请求类赋值给这个属性即可 :

from flask import Flask, Request
class MyRequest(Request):
    pass
app = Flask(__name__)
app.request_class = MyRequest

同样 , Flask 允许你使用自定义的响应类 。 在其内部 , 创建响应对象的 make_response() 并不是直接实例化 Response 类 , 而是实例化被存储在 Flask.response_class 属性上的类 , 默认为 Response 类 。 如果你要自定义响应类 , 创建后只需赋值给程序实例的 response_class 属性即可 。

结语

本文就先到此结束了,后面接着写。

如有错误,敬请指出,感谢指正!    — 2021-03-31  21:38:01

赞(0) 打赏
转载请注明:飘零博客 » 源码阅读 – Flask v0.1 [01] 阅读准备
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

欢迎光临