Flask的context stacks有什么用?

学习笔记:

学习大佬的回答,从Flask源码分析使用context stacks的原因。结论如下:

1. Flask的每个应用之间都是进程隔离的,在不同的wsgi工具下(例如gunicorn),一个worker进程中可能有多个线程(或协程)用于并发处理多个请求。在处理一个request时,需要获取当前线程的app,request等变量。考虑到线程隔离,就需要有一个线程隔离的对象来保存这些全局变量。

2. 为什么获取appapp,request等请求相关的变量要使用代理方式?

通常使用Local的方式来存储线程隔离的变量,为了方便管理,在一个线程中最好只维护一个Local对象,把不同的变量都存储在这个对象中即可。因此,为了方便管理,不同的变量只需要通过代理的方式,去获取当前线程中的Local,并从中获取需要的变量,而不用每个变量都要去管理Local对象。

2. 为什么使用栈格式?

为了兼容一个请求中需要多个request的情况,比如内部重定向时(资料很少,即通常不这样使用)就需要在一个request栈中压入多个对象。这里考虑旧的request你还不能丢,因为请求没有结束。

 

 

下文为答案摘录:

Local

Local实现线程隔离的对象存储,你可以在一个进程中拥有多个线程隔离的Local对象,也就可以在一个进程中存储多个request,g,current app和其他类似的对象。

简化后的代码如下:

class Local(object)
    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(),它可以标识当前的线程或协程。Local使用这个标识来区分不同线程。

 

LocalProxy

但是Flask中并没有直接使用Local对象来获取上述提到的对象,而是用LocalProxy(代理)。

LocalProxy通过查询当前线程中的Local对象来查找其中的对象。

初始化代理的时候:

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

初始化应用的时候:

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

 

优点:

1. 简化了对这些对象的管理,只需要一个单独的Local对象,就可以通过不同类型的代理对象去获取想要的值。请求结束后也只需要释放一个Local对象即可,不需要管理各种代理对象。

LocalStack

Flask中也没有直接只用LocalProxy,因为我们知道Flask在一个请求中可能会包含多个request(比如内部重定向),一个进程里也会处理多个应用上下文。虽然这些情况很少发生,但是Flask框架需要兼容这种情况,所以引入栈(LocalStack)的方式。

注意:LocalStack指Local里存储栈,栈里面保存多个线程隔离对象,通过代理方式访问。

class LocalStack(object):

    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) # this simply releases the 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

 

一个视图的请求初始化完成后,查找请求路径(request.path)的流程如下:

  • 从全局可访问LocalProxy对象request开始。
  • 为了找到其对应的对象(代理的对象),它将调用其查找函数_find_request()。
  • 该函数查询LocalStack对象_request_ctx_stack的栈顶的上下文对象。
  • 为了找到顶部的上下文对象,LocalStack对象首先在其Local属性(self.local)中查询先前存储在此处的stack属性。
  • 从stack中获得栈顶的上下文
  • top.request就这样作为当前的request对象。
  • 从request对象我们就可以得到path属性

参考:

https://stackoverflow.com/questions/20036520/what-is-the-purpose-of-flasks-context-stacks/20041823#20041823

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章