• Flask源码解析:Flask上下文


    一、上下文(Context)

    什么是上下文:

    每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。

    譬如说在C++的lambda表达是里面,[写在这里的就是上下文](int a, int b){ ... }。--- 引用  vczh

    当接受到一个请求时,我们通常将请求封装成一个HttpRequest对象,然后将对象传递给每一个视图函数,我们从request对象中获取相关的请求信息,这样做的缺点就是所有的视图函数都需要添加reqeust参数,即使视图函数中并没有使用到它。

    而在Flask中,将这些信息封装成类似全局变量的东西,视图函数需要的时候,可以使用from flask import request获取。但是这些对象和全局变量不同的是:他们必须是动态的,也就是说在多线程/多协程的情况下,每个线程获取的request都是属于自己的,不会相互干扰。

    Flask提供了应用上下文(Application Context)和请求上下文(Reqeust Context).

    上下文的有关定义在:flask/globals.py中

    def _lookup_req_object(name):
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name)
    
    
    def _lookup_app_object(name):
        top = _app_ctx_stack.top
        if top is None:
            raise RuntimeError(_app_ctx_err_msg)
        return getattr(top, name)
    
    
    def _find_app():
        top = _app_ctx_stack.top
        if top is None:
            raise RuntimeError(_app_ctx_err_msg)
        return top.app
    
    
    # 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'))

    Flask提供了两种上下文:request context 和 application context 。request context有衍生出request和session,appliction context衍生出current_app 和 g.

    请求上下文和应用上下文是通过LocalStack()和LocalProxy()来实现的

    LocalStack()和LocalProxy()是由Werkzeug提供,定义在werkzeug/local.py中,在分析这两个类之前,我们需要先分析另一个类Local。

    二、Local类

    Local类位于werkzeug/local.py中,它为LocalStack和LocalProxy提供了基础,Local就是实现了threading.local的效果——在多线程情况下全局变量的隔离效果。

    class Local(object):
        __slots__ = ('__storage__', '__ident_func__')
    
        def __init__(self):
            # 用于数据的保存
            object.__setattr__(self, '__storage__', {})
            # 用于获取当前线程的id
            object.__setattr__(self, '__ident_func__', get_ident)
    
        def __iter__(self):
            return iter(self.__storage__.items())
    
        def __call__(self, proxy):
            """Create a proxy for a name."""
            return LocalProxy(self, proxy)
    
        def __release_local__(self):
            """用于清空所有数据"""
            self.__storage__.pop(self.__ident_func__(), None)
    
        def __getattr__(self, name):
            try:
                # 通过线程id获取数据
                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:
                # 通过线程id绑定数据
                storage[ident][name] = value
            except KeyError:
                storage[ident] = {name: value}
    
        def __delattr__(self, name):
            try:
                del self.__storage__[self.__ident_func__()][name]
            except KeyError:
                raise AttributeError(name)

    存储形式键为线程/协程标识数值,值为字典类型的数据。

    三、LocalStack

    class LocalStack(object):
    
        def __init__(self):
            self._local = Local()
    
        def __release_local__(self):
            self._local.__release_local__()
    
        def _get__ident_func__(self):
            return self._local.__ident_func__
    
        def _set__ident_func__(self, value):
            object.__setattr__(self._local, '__ident_func__', value)
        __ident_func__ = property(_get__ident_func__, _set__ident_func__)
        del _get__ident_func__, _set__ident_func__
    
        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"""
            rv = getattr(self._local, 'stack', None)
            if rv is None:
    
                # 将数据保存到self._local.stack中
                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

    通过Local和LocalStack,我们可以了解到数据是以字典的形式存储在LocalStack中的,键为线程/协程id,值也是字典类型。LocalStack还实现了pushpoptop等方法。其中top方法永远指向栈顶的元素。栈顶的元素是指当前线程/协程中最后被推入栈中的元素。

    我们在flask/globals.py中看到请求上下文的定义,他就是一个LocalStack的实例:

    _request_ctx_stack = LocalStack()

    至于为什么要用到栈的结构,而不是直接使用Local()呢?

    四、LocalProxy类

    LocalProxy类是一个Local对象的代理,负责把所有对自己的操作都转发个内部的Local对象

    class LocalProxy(object):
        """Acts as a proxy for a werkzeug local.
        Forwards all operations to a proxied 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):
            """Return the current object."""
            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:
                raise AttributeError('__dict__')
    
        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

    这里实现的关键是把通过参数传递进来的 Local 实例保存在 __local 属性中,并定义了 _get_current_object() 方法获取当前线程或者协程对应的对象。

    继续回到request context的实现:

    _request_ctx_stack = LocalStack()

    request = LocalProxy(partial(_lookup_req_object, 'request'))

    session = LocalProxy(partial(_lookup_req_object, 'session'))

    _request_ctx_stack 是多线程或者协程隔离的栈结构

    request 每次都会调用 _lookup_req_object 栈头部的数据来获取保存在里面的 requst context。

  • 相关阅读:
    定时器与休眠
    进程的创建
    详述进程创建和程序执行
    线程
    会话,进程组,作业控制
    进程优先级和调度
    进程资源
    日志信息log
    能力
    浅谈产品模型(Profile)在程序设计中的作用
  • 原文地址:https://www.cnblogs.com/weihengblog/p/9546832.html
Copyright © 2020-2023  润新知