Python Web 模块之 Flask v0.1¶
第 2 部分 源码阅读准备¶
2.3 Flask 工作流程与机制¶
2.3.3 本地上下文¶
通过请求栈 _request_ctx_stack 的定义可以看到 , 确实是一个请求栈 , 而且是一个多线程隔离的请求中 。 在这边我们简单理解 LocalStack 是一个多线程安全的栈 , 提供 push , pop , top 的方法 。 而栈中元素必然就是单个请求了 , 元素类型为 _RequestContext :
[flask.py]
class _RequestContext(object):
def __init__(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ)
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()
self.flashes = None
def __enter__(self):
_request_ctx_stack.push(self)
def __exit__(self, exc_type, exc_value, tb):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
# access the request object in the interactive shell.
if tb is None or not self.app.debug:
_request_ctx_stack.pop()
看到单个请求使用 app 和 environ 进行初始化 , 其中 app 就是 Flask 实例 , environ 为单次请求具体信息 。 其中就包含 url_adapter 属性 , 前面已经介绍过 , 就是通过 url_adapter.match() 进行匹配后获取到 endpoint 和 values 的 , 从而获取到请求处理的视图函数的 , 从而与前面的解释相互印证 。 那么现在还剩下一个问题 , flask 是什么时候将 _RequestContext 加入到 _request_ctx_stack 中的呢 ? 让我们回头看一下 wsgi_app() 方法 , 使用 with 进行调用 :
class Flask(object):
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)
def request_context(self, environ):
return _RequestContext(self, environ)
可以看到调用了 request_context() 方法 , 此方法创建了一个 _RequestContext 对象 , 然后使用 with 的调用方式 , 会执行 _RequestContext 的 __enter__() 魔术方法 , 即会发现 _request_ctx_stack.push(self) , 将创建的 _RequestContext 加入请求栈 _request_ctx_stack 中 , 然后在执行处理结束的时候 , 执行 __exit__() 方法 , 将请求从请求栈中移除 。 至此 , 一切豁然开朗 。
2.3.3.1 本地线程与 Local¶
如果每次只能发送一封电子邮件 (单线程) , 那么在发送大量邮件时会花费很多时间 , 这时就需要使用多线程技术 。 处理 HTTP 请求的服务器也是这样 , 当我们的程序需要面对大量用户同时发起的访问请求时 , 我们显然不能一个个地处理 。 这时就需要使用多线程技术 , Werkzeug 提供的开发服务器默认会开启多线程支持 。
在处理请求时使用多线程后 , 我们会面临一个问题 。 当我们直接导入 request 对象并在视图函数中使用时 , 如何确保这时的 request 对象包含的请求信息就是我们需要的那一个 ? 比如 A 用户和 B 用户在同一时间访问 hello 视图 , 这时服务器分配了两个线程来处理这两个请求 , 如何确保每个线程内的 request 对象都是各自对应 、 互不干扰的 ?
解决办法就是引入本地线程 (Thread Local) 的概念 , 在保存数据的同时记录下对应的线程 ID , 获取数据时根据所在线程的 ID 即可获取到对应的数据 。 就像是超市里的存包柜 , 每个柜子都有一个号码 , 每个号码对应一份物品 。
Flask 中的本地线程使用 Werkzeug 提供的 Local 类实现 , 如代码清单 :
[wekzeug/local.py]
try:
from greenlet import getcurrent as get_current_greenlet
except ImportError: # pragma: no cover
try:
from py.magic import greenlet
get_current_greenlet = greenlet.getcurrent
del greenlet
except:
# catch all, py.* fails with so many different errors.
get_current_greenlet = int
class Local(object):
__slots__ = ('__storage__', '__lock__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__lock__', allocate_lock())
def __iter__(self):
return self.__storage__.iteritems()
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(get_ident(), None)
def __getattr__(self, name):
self.__lock__.acquire()
try:
try:
return self.__storage__[get_ident()][name]
except KeyError:
raise AttributeError(name)
finally:
self.__lock__.release()
def __setattr__(self, name, value):
self.__lock__.acquire()
try:
ident = get_ident()
storage = self.__storage__
if ident in storage:
storage[ident][name] = value
else:
storage[ident] = {name: value}
finally:
self.__lock__.release()
def __delattr__(self, name):
self.__lock__.acquire()
try:
try:
del self.__storage__[get_ident()][name]
except KeyError:
raise AttributeError(name)
finally:
self.__lock__.release()
Local 中构造函数定义了两个属性 , 分别是 __storage__ 属性和 __ident_func__ 属性 。 __storage__ 是一个嵌套的字典 , 外层的字典使用线程 ID 作为键来匹配内部的字典 , 内部的字典的值即真实对象 。 它使用 self.__storage__[self.__ident_func__()][name] 来获取数据 , 一个典型的 Local 实例中的 __storage__ 属性可能会是这样 :
{ 线程ID: { 名称: 实际数据}}
在存储数据时也会存入对应的线程 ID 。 这里的线程 ID 使用 __ident_func__ 属性定义的 get_ident() 方法获取 。 这就是为什么全局使用的上下文对象不会在多个线程中产生混乱 。
这里会优先使用 Greenlet 提供的协程 ID , 如果 Greenlet 不可用再使用 thread 模块获取线程 ID 。 类中定义了一些魔法方法来改变默认行为 。 比如 , 当类实例被调用时会创建一个 LocalProxy 对象 , 我们在后面会详细了解 。 除此之外 , 类中还定义了用来释放线程/协程的 __release_local__() 方法 , 它会清空当前线程/协程的数据 。
在 Python 类中 , 前后双下划线的方法常被称为魔法方法 (Magic Methods) 。 它们是 Python内置的特殊方法 , 我们可以通过重写这些方法来改变类的行为 。 比如 , 我们熟悉的 __init__() 方法 (构造函数) 会在类被实例化时调用 , 类中的 __repr__() 方法会在类实例被打印时调用 。 Local 类中定义的 __getattr__() 、 __setattr__() 、 __delattr__() 方法分别会在类属性被访问 、 设置 、 删除时调用 ; __iter__() 会在类实例被迭代时调用 ; __call__() 会在类实例被调用时调用 。 完整的列表可以在 Python 文档 (https://docs.python.org/3/reference/datamodel.html) 看到 。
2.3.3.2 堆栈与 LocalStack¶
堆栈或栈是一种常见的数据结构 , 它的主要特点就是后进先出 (LIFO,Last In First Out) , 指针在栈顶 (top) 位置 , 如图 16-9 所示 。 堆栈涉及的主要操作有 push (推入) 、 pop (取出) 和 peek (获取栈顶条目) 。 其他附加的操作还有获取条目数量 , 判断堆栈是否为空等 。 使用 Python 列表 (list) 实现的一个典型的堆栈结构如代码清单所示 。
[stack.py]
class Stack:
def __init__(self):
self.items = []
def push(self, item): # 推入条目
self.items.append(item)
def pop(self): # 移除并返回栈顶条目
if self.is_empty:
return None
return self.items.pop()
@property
def is_empty(self): # 判断是否为空
return self.items == []
@property
def top(self): # 获取栈顶条目
if self.is_empty:
return None
return self.items[-1]
承接上文 , 其中 push() 方法和 pop() 方法分别用于向堆栈中推入和删除一个条目 。 具体的操作示例如下 :
>>> class Stack:
def __init__(self):
self.items = []
def push(self, item): # 推入条目
self.items.append(item)
def pop(self): # 移除并返回栈顶条目
if self.is_empty:
return None
return self.items.pop()
@property
def is_empty(self): # 判断是否为空
return self.items == []
@property
def top(self): # 获取栈顶条目
if self.is_empty:
return None
return self.items[-1]
>>> s = Stack()
>>> s.push(42)
>>> s.top
42
>>> s.push(24)
>>> s.top
24
>>> s.pop()
24
>>> s.top
42
>>>
Flask 中的上下文对象正是存储在这一类型的栈结构中 , flask 这行代码创建了请求上下文堆栈 。
# context locals
_request_ctx_stack = LocalStack()
从这里可以想到 , 我们平时导入的 request 对象是保存在堆栈里的一个 _RequestContext 实例 , 导入的操作相当于获取堆栈的栈顶 (top) , 它会返回栈顶的对象 (peek操作) , 但并不删除它 。
这个堆栈对象使用 Werkzeug 提供的 LocalStack 类创建 , 如代码清单所示 。
class LocalStack(object):
def __init__(self):
self._local = Local()
self._lock = allocate_lock()
def __release_local__(self):
self._local.__release_local__()
def __call__(self):
def _lookup():
rv = self.top
if rv is None:
raise RuntimeError('object unbound')
return rv
return LocalProxy(_lookup)
def push(self, obj):
"""Pushes a new item to the stack"""
self._lock.acquire()
try:
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
finally:
self._lock.release()
def pop(self):
"""Removes the topmost item from the stack, will return the
old value or `None` if the stack was already empty.
"""
self._lock.acquire()
try:
stack = getattr(self._local, 'stack', None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()
finally:
self._lock.release()
@property
def top(self):
"""The topmost item on the stack. If the stack is empty,
`None` is returned.
"""
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
简单来说 , LocalStack 是基于 Local 实现的栈结构 (本地堆栈 , 即实现了本地线程的堆栈) , 和我们在前面编写的栈结构一样 , 有 push() 、 pop() 方法以及获取栈顶的 top 属性 。 在构造函数中创建了 Local() 类的实例 _local 。 它把数据存储到 Local 中 , 并将数据的字典名称设为 'stack' 。 注意这里和 Local 类一样也定义了 __call__ 方法 , 当 LocalStack 实例被直接调用时 , 会返回栈顶对象的代理 , 即 LocalProxy 类实例 。
这时会产生一个疑问 , 为什么 Flask 使用 LocalStack 而不是直接使用 Local 存储上下文对象 。 主要的原因是为了支持多程序共存 。 将程序分离成多个程序很类似蓝本的模块化分离 , 但它们并不是一回事 。 前面我们提到过 , 使用 Werkzeug 提供的 DispatcherMiddleware 中间件就可以把多个程序组合成一个 WSGI 程序运行 。
在上面的例子中 , Werkzeug 会根据请求的 URL 来分发给对应的程序处理 。 在这种情况下 , 就会有多个上下文对象存在 , 使用栈结构就可以让多个程序上下文存在 ; 而活动的当前上下文总是可以在栈顶获得 , 所以我们从 _request_ctx_stack.top 属性来获取当前的请求上下文对象 。
2.3.3.3 代理与 LocalProxy¶
代理 (Proxy) 是一种设计模式 , 通过创建一个代理对象 。 我们可以使用这个代理对象来操作实际对象 。 从字面理解 , 代理就是使用一个中间人来转发操作 。 代码清单是使用 Python 实现一个简单的代理类 。
class Proxy(object):
def __init__(self, obj):
object.__setattr__(self, '_obj', obj)
def __getattr__(self, name):
return getattr(self._obj, name)
def __setattr__(self, name, value):
self._obj[name] = value
def __delattr__(self, name):
del self._obj[name]
通过定义 __getattr__() 方法 、 __setattr__() 方法和 __delattr__() 方法 , 它会把相关的获取 、 设置和删除操作转发给实例化代理类时传入的对象 。 下面的操作演示了这个代理类的使用方法 。
>>> class Foo(object):
def __init__(self, x):
self.x = x
def bar(self, y):
self.x = y
>>> foo = Foo('Peter')
>>> p = Proxy(foo)
>>> p.x
'Peter'
>>> p
<__main__.Proxy object at 0x000002A81C6787C0>
>>> p._obj
<__main__.Foo object at 0x000002A81C678A00>
>>> p.bar('Grey')
>>> p.x
'Grey'
>>> foo.x
'Grey'
>>>
Flask 使用 Werkzeug 提供的 LocalProxy 类来实现代理 , 这是一个基于 Local 的本地代理 。 Local 类实例和 LocalStack 实例被调用时都会使用 LocalProxy 包装成一个代理 。 因此 , 下面的代码中的堆栈对象都是代理 。
_request_ctx_stack = LocalStack() # 请求上下文堆栈
如果要直接使用 LocalProxy 类实现代理 , 需要在实例化时传入一个可调用对象 , 比如传入的 lambda: _request_ctx_stack.top.request :
request = LocalProxy(lambda: _request_ctx_stack.top.request)
LocalProxy 的定义如代码清单所示 :
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__')
def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
def _get_current_object(self):
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
return AttributeError('__dict__')
def __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj)
def __nonzero__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
def __unicode__(self):
try:
return unicode(self._get_current_object())
except RuntimeError:
return repr(self)
def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return []
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __delitem__(self, key):
del self._get_current_object()[key]
def __setslice__(self, i, j, seq):
self._get_current_object()[i:j] = seq
def __delslice__(self, i, j):
del self._get_current_object()[i:j]
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
__str__ = lambda x: str(x._get_current_object())
__lt__ = lambda x, o: x._get_current_object() < o
__le__ = lambda x, o: x._get_current_object() <= o
__eq__ = lambda x, o: x._get_current_object() == o
__ne__ = lambda x, o: x._get_current_object() != o
__gt__ = lambda x, o: x._get_current_object() > o
__ge__ = lambda x, o: x._get_current_object() >= o
__cmp__ = lambda x, o: cmp(x._get_current_object(), o)
__hash__ = lambda x: hash(x._get_current_object())
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
__len__ = lambda x: len(x._get_current_object())
__getitem__ = lambda x, i: x._get_current_object()[i]
__iter__ = lambda x: iter(x._get_current_object())
__contains__ = lambda x, i: i in x._get_current_object()
__getslice__ = lambda x, i, j: x._get_current_object()[i:j]
__add__ = lambda x, o: x._get_current_object() + o
__sub__ = lambda x, o: x._get_current_object() - o
__mul__ = lambda x, o: x._get_current_object() * o
__floordiv__ = lambda x, o: x._get_current_object() // o
__mod__ = lambda x, o: x._get_current_object() % o
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
__pow__ = lambda x, o: x._get_current_object() ** o
__lshift__ = lambda x, o: x._get_current_object() << o
__rshift__ = lambda x, o: x._get_current_object() >> o
__and__ = lambda x, o: x._get_current_object() & o
__xor__ = lambda x, o: x._get_current_object() ^ o
__or__ = lambda x, o: x._get_current_object() | o
__div__ = lambda x, o: x._get_current_object().__div__(o)
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
__neg__ = lambda x: -(x._get_current_object())
__pos__ = lambda x: +(x._get_current_object())
__abs__ = lambda x: abs(x._get_current_object())
__invert__ = lambda x: ~(x._get_current_object())
__complex__ = lambda x: complex(x._get_current_object())
__int__ = lambda x: int(x._get_current_object())
__long__ = lambda x: long(x._get_current_object())
__float__ = lambda x: float(x._get_current_object())
__oct__ = lambda x: oct(x._get_current_object())
__hex__ = lambda x: hex(x._get_current_object())
__index__ = lambda x: x._get_current_object().__index__()
__coerce__ = lambda x, o: x.__coerce__(x, o)
__enter__ = lambda x: x.__enter__()
__exit__ = lambda x, *a, **kw: x.__exit__(*a, **kw)
未完待续 ...
上一篇文章 : 上一篇
下一篇文章 : 下一篇