Flask 源码之local, localstack

Local:

在之前一篇博数据库连接池中,提到了local的用法,那么本篇来看看flask中Local类的实现,我们看看它的源码中是怎么实现,以及它的执行过程:

首先我们导入它:它在werkzeug下的local模块中,这里将它的代码粘贴出来 :

#!usr/bin/env python
# *- coding:utf-8 -*-
# Author: Andy
from flask import globals

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

"""
上面这段代码表明,当使用了协程时,get_indent 是从协程中取,即获取协程的id,如果没有使用协程,那么从线程中获取线程的唯一id
下面需要用到这个id,即get_ident
"""

class Local(object):
    __slots__ = ("__storage__", "__ident_func__")
    # 能通过点号获取的值

    def __init__(self):
        # 实例化时  self.__storage__ = {}
        # self.__ident_func__ = get_ident
        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)

下面我们分析一下使用local的情况,看看它发生了什么:


实例化:
当我们实例化对象,比如local = Local()
此时执行Local对象的__init__方法. local.__storage__ = {}, local.__ident_func__ = get_ident,


赋值:
现在我对local赋值,local.a = 1, 即执行__setattr__方法, ident = self.__ident_func__()=get_ident(),获取到当前进程的id,假设为1111,
storage = self.__storage__ = {}, storage[ident][name] = value, 如果storage中有键为1111的字典,且1111这个字典中存在为a
 的键,则对它赋值。如果不存在storage['1111']={'a':1},结果就是对当前线程id生成一个字曲。
 

取值:
 取值时执行__getattr__方法,self.__storage__[self.__ident_func__()][name] == storage[get_ident()}[name]即 storage['1111']['a']
 这样便取到了值1。


 __delattr__则是将数据销毁, 同样release_local则是将对应的值从字典中pop出来 
 

整个过程非常清晰明了,与数据库连接池中一样,都是维护一个字典,其中以线程id为键,字典中保存键和什。取值时也根据线程id来获取这个字典中的键值。因为线程(或者协程)的id是唯一的,也就保证了数据和独立。事实上flask内部还有一个LocalStack类。

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 = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        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):
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

def release_local(local):
    """Releases the contents of the local for the current context.
        >>> loc = Local()
        >>> loc.foo = 42
        >>> release_local(loc)
        >>> hasattr(loc, 'foo')
        False
    """
    local.__release_local__()
可以看到它的get_ident, release_local等方法是使用的Local类的方法,这里略过。
现在我们分析它的执行过程
local_stack = Local(),执行它的__init__方法,self._local = Local(), 即执行了上面那个Local类的实例化,也就是也生成了一个字典

现在假如我执行push方法,比如local_stack.push('andy'), stack = getattr(self._local, "stack", None),即stack=getattr(Local(), "stack", None)
取Local类对象的的stack的值,所以执行Local类的getattr方法,第一次很执行,因为__storage__中还没有这个’stack'键,所以出错,取不到值,因为rv=None
rv=None, 因此执行:self._local.stack = rv = [] , Local().stack = [],即对字典赋值, {__storage__, {'stack':[]}}=__storage={'stack':[]}
然后将值添加到这个列表中,即:__storage__={'stack':['andy',]}

top方法, self._local.stack[-1]=Local().stack[-1],即取stack字典中的值, getattr方法而stack对应一个列表rv,取最后一个值,即栈顶。

pop方法:stack = getattr(Local(), 'stack',None), 去字典里获取 stack,如果第一个pop,则返回None,因为没有stack。 本例中列表里有个
['andy',], 即stack=['andy',], 因此会执行stack.pop(),即把andy从列表中删除,另外 len(stack) ==1 也成立,releas_local(Local()),release_local是一个独立方法,
但它实例是调用了Local类的__release_local__方法,也就会执行Local类的
__elease_loal__方法,self.__storage__.pop(self.__ident_func__(), None), 可以看到它将当前线程id组成的字典从__storage__中pop掉了,也就是将当前线程的数据销毁 了

可以看到,在Flask内部有两个类,一个Local, 一个LocalStack, Local类的功能和threading.local功能一样,为每个线程开辟空间用来存放数据,其内部实现机制都是维护一个字典,以线程(协程)的id为键,进行数据隔离。而LocalStack类则依赖local类的功能,并将它维护成一个栈

上一篇:Django contenttype 组件使用 及源码分析

下一篇:Flask源码之实例化Flask对象