Flask源码分析(三):上下文的作用和实现原理

Flask源码解析(二):Flask的工作流程中说过,关于上下文的内容会专门写一篇文章来讲,今天就把这个内容写一写。再次声明,本文涉及到的Flask源码都是出自Flask0.1的源码,部分代码为了方便理解只保留核心部分。

首先,在了解上下文之前,必须要弄清楚Local、LocalStack、LocalProxy这三个概念。

Local

Python中的thread locals实现了线程隔离的数据访问方式,但是在web应用中存在以下问题:

  • 多个greenlet协程可以在同一个线程中,所以无法保证协程之间数据的隔离
  • 多个http请求会复用线程,之前请求的数据会残留在thread locals中

WerkZeug的Local类解决了这两个问题。

Local的实现

try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        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:
            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}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
  1. Local类使用__storage__字典实现greentlet或thread之间的数据隔离,这个字典的key是greenlet id或thread id(有greenlet模块时是greent id)
  2. 通过重写__getattr__和__setattr__方法,当我们使用Local对象访问属性时,会自动获取当前greentlet或thread存储空间里的属性值
  3. 通过__release_local__方法可以释放当前greentlet或thread的数据

LocalStack

LocalStack是基于Local实现的栈,提供了栈的push,pop,top方法。

LocalStack的实现

class LocalStack(object):

    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    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 = 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
  1. LocalStack内部维护的是_local对象的stack属性,所以它在greentlet或thread之间是数据隔离的
  2. 当栈中元素为空时,会自动调用release_local方法释放当前greentlet或thread的数据

LocalStack的用法

>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23
>>> ls.top
42

LocalProxy

LocalProxy用于代理Local对象和LocalStack对象

LocalProxy的实现

@implements_bool
class LocalProxy(object):
    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')

    def __init__(self, local, name=None):
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        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 __repr__(self):
        try:
            obj = self._get_current_object()
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__
        return repr(obj)

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

    def __unicode__(self):
        try:
            return unicode(self._get_current_object())  # noqa
        except RuntimeError:
            return repr(self)

    def __dir__(self):
        try:
            return dir(self._get_current_object())
        except RuntimeError:
            return []

    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]
        
    ......
  1. LocalProxy重载了所有的基本方法,使得对LocalProxy对象执行的任何操作,都是通过是_get_current_object来获取被代理对象,然后执行相应的操作,从而实现了动态更新被代理对象的效果

LocalProxy的用法

使用Local、LocalStack对象的__call__ 方法
from werkzeug.local import Local
l = Local()

# a proxy to whatever l.user is set to
user = l("user")

from werkzeug.local import LocalStack
_request_stack = LocalStack()

# a proxy to _request_stack.top
request = _request_stack()
使用Local对象作为参数
l = Local()
user = LocalProxy(l, 'user')
使用callable对象作为参数
# a proxy to the session attribute of the request proxy
session = LocalProxy(lambda: request.session)

了解完Local、LocalStack、LocalProxy,接下来看一下上下文。

什么是上下文

程序中的上下文代表了程序当下所运行的环境,存储了一些程序运行的信息。

Flask中的上下文

在Flask0.1中只有请求上下文,请求上下文中的对象有:

  1. request:封装了HTTP请求的内容,针对的是http请求。比如request.args.get(‘user’)可以获取get请求的参数。
  2. session:用来记录请求会话中的信息,针对的是用户信息。比如session[‘name’] = user.id可以记录用户信息。
  3. current_app:表示当前运行程序文件的程序实例,比如可以通过current_app.name打印出当前应用程序实例的名字。
  4. g:用于临时存储的对象,每次请求都会重置这个变量。比如可以通过它传递一些临时数据。

特别说明:在Flask0.9增加了应用上下文并将current_app移动到应用上下文中,在Flask1.0中g对象也被移动到应用上下文中。

Flask中上下文的定义

_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)

上下文处理流程

在上一篇文章Flask源码解析(二):Flask的工作原理中说到,wsgi_app方法中会创建上下文:

def wsgi_app(self, environ, start_response):
    with self.request_context(environ):
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
        response = self.make_response(rv)
        response = self.process_response(response)
        return response(environ, start_response)

request_context()方法中会创建一个_RequestContext对象,_RequestContext源码如下:

class _RequestContext(object):
    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()
        self.flashes = None

    def __enter__(self):
        _request_ctx_stack.push(self)

    def __exit__(self, exc_type, exc_value, tb):
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

从上面的代码可以看出,with self.request_context(environ)的作用就是对每个http请求,先创建请求上下文对象,放到_request_ctx_stack栈对象中,在响应返回之后把请求上下文对象弹出,这样在视图函数处理期间就可以从栈上获取request、session、current_app、g这几个对象

疑问解答

1.在Web应用运行时一个线程同时只处理一个请求,那么 _req_ctx_stack和 _app_ctx_stack肯定都是只有一个栈顶元素的。那么为什么还要用栈这种结构?

答:Flask的设计理念之一就是多应用的支持。当在一个应用的上下文环境中,需要嵌套处理另一个应用的相关操作时,这时就能体现出栈的好处了,只要通过调用_request_ctx_stack.top.app或者current_app可以获得当前上下文环境正在处理哪个应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值