flask中current_app、g、request、session源码的深究和理解

本文是我在学习flask中对上下文和几个相似全局变量的思考和研究,也有我本身的理解在内。python

为了研究flask中的current_app、g、request、session,我找到定义在global.py的源码:编程

# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

能够看到主要由_lookup_req_object、_lookup_app_object、_find_app等组成,我先来分析request和session
其实request和session原理上是同样的,因此将其归为一类,称为请求上下文。flask

咱们从最里面看起,partial(_lookup_req_object, 'request'),最外层是一个偏函数,不过这不是重点,它主要是将'request'传给_lookup_req_object,没有其余含义, 顺着_lookup_req_object找到它的源码服务器

def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
    raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)

从最后的return能够看到,这个函数的主要功能是从top中取出键值为'request'的内容,top是一个字典,top从_request_ctx_stack.top中来,在上面的源码中 _request_ctx_stack = LocalStack(),从名字来看LocalStack应该是一个栈类,应该有pop,push,top方法,我继续找到源码:cookie

def __init__(self):
    self._local = Local()
...
...
def push(self, obj):
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
        self._local.stack = rv = []
    rv.append(obj)
    return rv

def pop(self):
    """Removes the topmost item from the stack, will return the
    old value or `None` if the stack was already empty.
    """
    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()

@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()这个类有一个属性self._local = Local(),对应另外一个类,继续看源码:session

def __init__(self):
    object.__setattr__(self, '__storage__', {})
    object.__setattr__(self, '__ident_func__', get_ident)
...
...
def __getattr__(self, name):
    try:
        return self.__storage__[self.__ident_func__()][name]
    except KeyError:
        raise AttributeError(name)

def __setattr__(self, name, value):
    ident = self.__ident_func__()
    storage = self.__storage__
    try:
        storage[ident][name] = value
    except KeyError:
        storage[ident] = {name: value}

我截取了几个重要的函数,LocalStack()中的push,用到了Local()中的__setattr__();pop用到了__getattr__(),看到push和pop都是对'stack'这个键值进行查询和赋值,咱们转到Local()这个类中,这个类有两个实例属性,__storage__和__ident_func__,前者是一个字典,后者是一个函数,咱们看一下这个get_ident函数,查看源码:app

def get_ident(): # real signature unknown; restored from __doc__
    """
    get_ident() -> integer

    Return a non-zero integer that uniquely identifies the current thread
    amongst other threads that exist simultaneously.
    This may be used to identify per-thread resources.
    Even though on some platforms threads identities may appear to be
    allocated consecutive numbers starting at 1, this behavior should not
    be relied upon, and the number should be seen purely as a magic cookie.
    A thread's identity may be reused for another thread after it exits.
    """
    return 0

显然这个函数不是python写的,由于它来自_thread.py,是一个底层库,从名字能够猜到和线程有关,根据描述,它返回一个非零整数,表明了当前线程id,咱们再看看__setattr__这个方法,它实际上是一个字典嵌套列表再嵌套字典的数据,__storage__是一个字典,它里面的键值被赋值为当前线程id,这个键值对应的值是另外一个字典:{'stack':['request':r_val,'session':s_val]},这样和前面联系起来就很好理解了,Local()中的__storage__存储的格式为{thread_id:{'stack':['request':r_val,'session':s_val]}},LocalStack()中的top方法,返回了'stack'中最后一个加入的元素,也就是最新的元素,我本身理解为服务器接受的最新的请求,在框架外看起来request和session是一个全局变量,其实内部已经由进程id将其分隔开了,即便同时有多个请求过来,进程间的数据也不会混乱。框架

同理current_app和g也同样,惟一不一样的是,current_app、g和request、session是两个不一样的实例,注意前面 _request_ctx_stack = LocalStack()、_app_ctx_stack = LocalStack(),因此'stack'中存的数据也不同,current_app和g称为应用上下文,二者仍是有区别的。
LocalProxy 则是一个典型的代理模式实现,它在构造时接受一个 callable 的参数(好比一个函数),这个参数被调用后的返回值自己应该是一个 Thread Local 对象。对一个 LocalProxy 对象的全部操做,包括属性访问、方法调用(固然方法调用就是属性访问)甚至是二元操做,都会转发到那个 callable 参数返回的 Thread Local 对象上。
LocalProxy 的一个使用场景是 LocalStack 的 call 方法。好比 my_local_stack 是一个 LocalStack 实例,那么 my_local_stack() 能返回一个 LocalProxy 对象,这个对象始终指向 my_local_stack 的栈顶元素。若是栈顶元素不存在,访问这个 LocalProxy 的时候会抛出 RuntimeError。
须要注意的是,若是须要离线编程,尤为在写测试代码时,须要将应用上下文push到栈中去,否则current_app会指向空的_app_ctx_stack栈顶,天然也就没法工做了。
咱们能够经过current_app的值来判断是否进入应用上下文中,能够用app.app_context().push()来进入应用上下文。ide

相关文章
相关标签/搜索