Flask0.1源码(1)-五个全局对象

flask-0.1 源码中有五个全局对象, 分别是: _request_ctx_stack, current_app, request, sessiong. 从源码能够看出, 其他四个都会依赖于 _request_ctx_stack. 单从命名上来看, 这应该是一个用于存放请求上下文的栈.python

# flask-0.1 中不区分 app_ctx 和 request_ctx

# 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)
复制代码

这就让我很是不解了.git

不解

咱们先抛开具体栈的实现, 仅仅当它是一个栈来考虑, 而且忽略多线程. 只有一个线程的状况下, Flask 同一时间只能处理一个请求, 那么只须要一个请求上下文实例也能够达到一样的效果, 为何会用栈来实现呢?
Stack Overflow 上有一个小哥和我同样不解, 问题下面的回答很是精彩(建议回答和评论都仔细看一下).

首先回答我不解的是 Flask 中的 url_forredrict. Flask 是支持在内部直接重定向的, 用栈来存放请求上下文能够很方便地支持这一特性, 可是若是仅仅是用一个请求上下文实例而不是栈的话倒是很难实现的. 举个🌰:
若是 A 请求过来, 你须要在处理 A 请求的时候重定向到 B, 这个时候, 若是是栈的话, 当前请求就会被"挂起", B 请求会被压入栈中, 等待 B 请求处理完, 从栈中 pop 出来以后, 就能够继续处理 A 请求, 或者将 B 请求的结果做为 A 请求的结果返回; 可是若是只是一个实例的话, A 请求上下文就会被 B 替换, 就会形成 A 请求没法返回.github


搞清楚为何用栈来储存请求上下文以后, 咱们再来栈的具体实现.

要搞清楚这是一个什么栈, 就得引入多线程了. 若是咱们以多个线程启动 Flask, 那即可以同时处理多个请求, 可是 Flask 实例只有一个, 那它又是如何处理请求上下文的呢? 它是怎么作到不一样线程上请求上下文分离的呢?flask

仔细观察上面的源码部分, 能够发现 Flask 的全局对象 _request_ctx_stack 是 Werkzeug LocalStack 类的一个实例. 可见, 不一样线程请求上下文分离的奥秘应该隐藏在 LocalStack 这个类中.
为了理解 LocalStack,咱们先引入 Werkzeug 的另一个类 --- Local.
简单来讲, Local 实现的功能是同一个实例在不一样线程(Werkzeug支持线程和 greenlet)下变量的分离. 好比下面的例子中, Local 的实例是全局的, 可是在不一样的线程(greenlet)下, 实例中变量的值能够是不一样的. 当你在线程 1 中访问 local.name 的时候, 返回的值是 John, 可是在线程 2 中返回的倒是 'Debbie':session

local = Local()

# ...

# on thread 1
local.name = 'John'

# ...

# on thread 2
local.name = 'Debbie'
复制代码

那这是怎么作到的呢? 简化版的 Local 实现以下:多线程

"""
Local 源码:  
https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L51
"""

# 简化版 Local 实现
class Local:
    def __init__(self)
        self.storage = {}
    
    def __getattr__(self, name):
        context_id = get_ident()  # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError('name')
    
    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value
    
    
    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)
        
local = Local()
复制代码

代码中 get_ident() 起着相当重要的做用, 它可以识别出当前所在的线程, 而且将当前所在线程的上下文变量储存在实例字典中, 以线程 id 做为 key, 从而作到了不一样线程下上下文变量的分离.app

理解 Local 后, 继续看 Flask 中用来实现请求栈的 LocalStack. 先看看看简化版的 LocalStack 实现:ide

""" LocalStack 源码: https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L89 """

# 简化版 LocalStack
class LocalStack:
    def __init__(self):
        self.local = Local()
    
    def push(self, obj):
        """ 将新元素 push 到栈中 """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            stack = []
            self.local.stack = stack
        stack.append(obj)
        return stack
    
    def pop(self):
        """ pop 出栈顶元素 若是栈为空, 返回 None """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) = 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return tack.pop()
    
 @property
    def top(self):
        """ 获取站顶元素, 不出栈 若栈为空, 一样返回 None """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None
复制代码

LocalStackLocal 包了一层(在 Local 中保存了一个栈), 因此依然支持不一样线程变量分离. 每一个线程中都维护了一个请求栈, 且各个栈互不干扰. 而且实现了 push, poptop 方法, 分别对应进栈, 出栈和获取栈顶元素三中常见栈操做.this


另外, 其实 Flask 的这几个全局变量仍是和 Local, LocalStack 不太同样, 它是用 LocalProxy 实现的. 那 LocalProxy 又是什么?
讲真, 这个困惑了我好久.
首先仍是解释一下 LocalProxy 的做用吧. 就如它命名所示, 它起到的就是一个代理的做用.
就拿上面文中的 request 举例来讲, LocalProxy 的做用就是把全部对 request 的操做所有代理到请求栈顶元素(_request_ctx_stack.top.request)中.url

""" LocalProxy 源码: https://github.com/pallets/werkzeug/blob/5d80fa2cd1008abaa9450b40a44f0df90a842489/werkzeug/local.py#L254 """

# 简化版 LocalProxy
class LocalProxy(object):
    def __init__(self, local, name):
        # local 是一个 Local 实例
        # 或者是可回调的, 经过它能够获取到被代理的对象
        self.local = local
        # `name` 是被代理的对象的名字(key)
        self.name = name

    def _get_current_object(self):
        # 若是 local 是一个 Local 实例, 则他会有 `__release_local__` (被用于释放 local 对象)
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # 若是不是 Local 实例, 就必须能够经过直接调用它获取到被代理的对象
        return self.local(self.name)

    # 如下全部的魔术方法都被重写
    # 使得对 LocalProxy 实例的全部操做都会被代理到被代理的对象
 @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

    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]

    # ... and so on ...

    __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

    # ... and so forth ...
复制代码

LocalProxy 重写了不少魔术方法, 使得全部对其实例的操做所有转移到被代理的对象上.

参考:
What is the purpose of Flask's context stacks?
werkzeug.local
flask 源码解析:上下文

相关文章
相关标签/搜索