源码网站 g-Flask中current_app、g、request、session源码的考虑

这篇文章是我对学习flask中context以及几个类似的全局变量的思考和研究,包括我自己的理解。

为了研究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'))

源码网站 g-Flask中current_app、g、request、session源码的考虑

可以看到主要是由_lookup_req_object、_lookup_app_object、_find_app等组成,我先分析一下request和session

其实request和session在原理上是一样的,所以归为一类,称为请求上下文。

我们从上往下看,partial(_lookup_req_object, 'request'),最内层是一个partial函数,但这不是重点,它主要是将'request'传递给_lookup_req_object,没有其​​他意义,跟随_lookup_req_object查找它的源代码

源码网站 g-Flask中current_app、g、request、session源码的考虑

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

从最终返回可以看出,该函数的主要作用是从top中取出通配符为'request'的内容,top是一个字典,top来自_request_ctx_stack.top,在源码里面_request_ctx_stack = LocalStack() ,从名字上看,LocalStack应该是一个栈类,应该有pop、push、top方法,我继续找源码:

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

源码网站 g-Flask中current_app、g、request、session源码的考虑

可以看到LocalStack()类有一个属性self._local = Local(),它对应于另一个类。 继续看源码:

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}

我截取了几个重要的函数,push in LocalStack()在Local()中使用了__setattr__(); pop使用了__getattr__(),我看到push和pop都是对通配符'stack'进行查询和形参,我们转移到Local()类。 该类有两个实例属性:__storage__ 和 __ident_func__。 前者是字典,后者是函数。 我们先看一下get_ident函数,查看源码:

源码网站 g-Flask中current_app、g、request、session源码的考虑

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]},这样就很容易理解和上面的联系了源码网站 g,Local中__storage__存储的格式( ) 为 {thread_id:{'stack' :['request':r_val,'session':s_val]}},LocalStack() 中的 top 方法返回 'stack' 中最后添加的元素,即最新元素,我理解为服务器接受到的最新请求,request和session在框架外看似是一个全局变量,但实际上内部已经通过process id进行了分隔。 即使同时有多个请求,进程之间的数据也不会混乱。

current_app 和 g 也是如此。 唯一的区别是 current_app、g、request 和 session 是两个不同的实例。 注意上面的 _request_ctx_stack = LocalStack() 和 _app_ctx_stack = LocalStack()源码网站 g,所以 'stack' 中存储的数据是不一样的,current_app 和 g 称为应用程序上下文,两者还是有区别的。

LocalProxy是一个典型的代理模式实现。 它在构造时接受可调用参数(例如函数)。 该参数调用后的返回值本身应该是一个Thread Local对象。 对 LocalProxy 对象的所有操作,包括属性访问、方法调用(当然方法调用就是属性访问)甚至二进制操作,都会被转发到 callable 参数返回的 Thread Local 对象。

LocalProxy的一种使用场景是LocalStack的调用方法。 例如,my_local_stack是一个LocalStack实例,那么my_local_stack()可以返回一个LocalProxy对象,该对象仍然指向my_local_stack的顶部元素。 如果栈顶元素不存在,则访问此LocalProxy时会抛出RuntimeError。

需要注意的是,如果需要离线编程,尤其是编写测试代码时,需要将应用程序上下文压入栈中,否则current_app会指向空的_app_ctx_stack栈顶,自然就很难工作了。

我们可以通过current_app的值来判断是否进入应用程序上下文,我们可以使用app.app_context().push()来进入应用程序上下文。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 网站源码 源码网站 g-Flask中current_app、g、request、session源码的考虑 https://www.wkzy.net/game/147299.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务