python框架之Flask(4)-上下文管理

知识储备

偏函数

  • 作用

    偏函数,帮助开发者自动传递参数。

  • 使用

    import functools
    
    
    def index(a1, a2):
        return a1 + a2
    
    
    # 原来的调用方式
    # ret = index(1,23)
    # print(ret)
    
    # 偏函数,帮助开发者自动传递参数
    new_index = functools.partial(index, 666)
    ret = new_index(1)
    ret = new_index(1)
    print(ret)
    例:

super

class Base(object):

    def func(self):
        super(Base, self).func()
        print('Base.func')


class Bar(object):
    def func(self):
        print('Bar.func')


class Foo(Base, Bar):
    pass


# 示例一
obj = Foo()  # Bar.func
obj.func()  # Base.func
print(Foo.__mro__)  # (<class '__main__.Foo'>, <class '__main__.Base'>, <class '__main__.Bar'>, <class 'object'>)

# 示例二
obj = Base()
obj.func()  # AttributeError: 'super' object has no attribute 'func'
例:

 super 在我们心里可能都默认它是用来执行父类方法,但在 python 中,这句话只在单继承时成立。看输出结果,其实 super 的执行顺序是根据执行对象 __mro__ 属性的结果顺序来的

面向对象的特殊方法

class Foo(object):
    def __init__(self):
        # 执行这行时会报错: AttributeError: 'Foo' object has no attribute 'storage'
        # 因为 self.storage 的赋值就是由 __setattr__ 方法完成的,而在 __setattr__ 方法中直接使用 self.storage ,并没有赋值操作,肯定会因为没有这个属性报错
        # self.storage = {}
        object.__setattr__(self, 'storage', {})

    def __setattr__(self, key, value):
        print(key, value, self.storage)


obj = Foo()
obj.v = 123
# v 123 {}
例:

基于列表实现栈

class Stack(object):

    def __init__(self):
        self.data = []

    def push(self, val):
        self.data.append(val)

    def pop(self):
        return self.data.pop()

    def top(self):
        return self.data[-1]


_stack = Stack()

_stack.push('v1')
_stack.push('v2')

print(_stack.pop())
print(_stack.pop())
例:

threading.local

  • 作用

    为每个线程创建一个独立的空间,使得线程对自己空间中的数据进行操作(数据隔离)。

  • 示例

    看如下示例:

    import threading
    import time
    
    v = 0
    
    
    def task(i):
        global v;
        v = i
        time.sleep(1)
        print(v)
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    
    '''
    9
    9
    9
    9
    9
    9
    9
    9
    9
    9
    '''
    例:

    当通过多线程对同一份数据进行修改时,会出现如上覆盖的现象。以往我们解决这种问题一般时通过加锁,而 threading.local 其实也可以为我们解决这种问题,如下:

    import threading
    import time
    from threading import local
    
    obj = local()
    
    
    def task(i):
        obj.v = i
        time.sleep(1)
        print(obj.v)
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    
    '''
    0
    5
    3
    1
    6
    2
    4
    9
    7
    8
    '''
    例:

    可以看到并没有发生值覆盖的问题。

  • 原理

    修改上述第一个示例如下:

    import threading
    import time
    
    dic = {}
    
    
    def task(i):
        # 获取当前线程的唯一标记
        ident = threading.get_ident()
    
        if ident in dic:
            dic[ident]['v'] = i
        else:
            dic[ident] = {'v': i}
        time.sleep(1)
        print(dic[ident]['v'])
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    
    '''
    0
    1
    3
    5
    2
    4
    9
    8
    6
    7
    '''
    例:第一版

    其实原理很简单,一个字典,通过获取正执行线程的唯一标记作为键,通过这个键就能够区分开线程隔离保存数据。

    同理,按上述原理,我们也可以将其改为根据协程隔离数据:

    import threading
    import greenlet
    import time
    
    dic = {}
    
    
    def task(i):
        # 获取当前协程的唯一标记
        ident = greenlet.getcurrent()
        if ident in dic:
            dic[ident]['v'] = i
        else:
            dic[ident] = {'v': i}
        time.sleep(1)
        print(dic[ident]['v'])
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    
    '''
    0
    1
    3
    5
    2
    4
    9
    8
    6
    7
    '''
    例:第二版

    还可以将其进行对象的封装,使其的使用更接近于 threading.local ,如下:

    import time
    import threading
    
    try:
        import greenlet
    
        get_ident = greenlet.getcurrent
    except Exception as e:
        get_ident = threading.get_ident
    
    
    class Local(object):
        dic = {}
    
        def __getattr__(self, item):
            ident = get_ident()
            if ident in self.dic:
                return self.dic[ident].get(item)
            return None
    
        def __setattr__(self, key, value):
            ident = get_ident()
            if ident in self.dic:
                self.dic[ident][key] = value
            else:
                self.dic[ident] = {key: value}
    
    
    obj = Local()
    
    
    def task(i):
        obj.v = i
        time.sleep(1)
        print(obj.v)
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i,))
        t.start()
    
    '''
    5
    3
    1
    6
    4
    2
    0
    9
    7
    8
    '''
    例:终极版

localstack

import functools

try:
    from greenlet import getcurrent as get_ident
except:
    from threading 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 __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)


class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def push(self, value):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(value)
        return rv

    def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            return stack[-1]
        else:
            return stack.pop()

    def top(self):
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

stack = LocalStack()
stack.push('aaa')
print(stack.pop())
例:

以上综合应用

通过以上知识来模拟 Flask 中 session 和 request 的存储。

import functools

try:
    from greenlet import getcurrent as get_ident
except:
    from threading 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 __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)


class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def push(self, value):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(value)
        return rv

    def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            return stack[-1]
        else:
            return stack.pop()

    def top(self):
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None


class RequestContext(object):
    def __init__(self):
        self.request = 'request'
        self.session = 'session'


_request_ctx_stack = LocalStack()

_request_ctx_stack.push(RequestContext())


def _lookup_req_object(arg):
    ctx = _request_ctx_stack.top()
    return getattr(ctx, arg)


request = functools.partial(_lookup_req_object, 'request')
session = functools.partial(_lookup_req_object, 'session')

print(request())
print(session())
例:

上下文管理

request上下文

之前说Session原理时,在执行 flask.app.Flask.wsgi_app 方法时其中有一个 ctx 变量,如下:

 1 def wsgi_app(self, environ, start_response):
 2     '''
 3     获取environ并对其进行封装
 4     从environ中获取名为session的cookie,解密并反序列化
 5     放入请求上下文
 6     '''
 7     ctx = self.request_context(environ)
 8     error = None
 9     try:
10         try:
11             ctx.push()
12             '''
13             执行视图函数
14             '''
15             response = self.full_dispatch_request()
16         except Exception as e:
17             error = e
18             response = self.handle_exception(e)
19         except:
20             error = sys.exc_info()[1]
21             raise
22         return response(environ, start_response)
23     finally:
24         if self.should_ignore_error(error):
25             error = None
26         '''
27         获取session,解密并序列化,写入cookie
28         清空请求上下文
29         '''
30         ctx.auto_pop(error)
flask.app.Flask.wsgi_app

一个网站程序是可能有多个用户也就是多个线程同时访问的,之所以上面会提 threading.local 知识,正是因为 ctx 就是以这种方式保存的。看 ctx.push 方法:

 1 def push(self):
 2     top = _request_ctx_stack.top
 3     if top is not None and top.preserved:
 4         top.pop(top._preserved_exc)
 5 
 6     app_ctx = _app_ctx_stack.top
 7     if app_ctx is None or app_ctx.app != self.app:
 8         app_ctx = self.app.app_context()
 9         app_ctx.push()
10         self._implicit_app_ctx_stack.append(app_ctx)
11     else:
12         self._implicit_app_ctx_stack.append(None)
13 
14     if hasattr(sys, 'exc_clear'):
15         sys.exc_clear()
16 
17     _request_ctx_stack.push(self)
18 
19     if self.session is None:
20         session_interface = self.app.session_interface
21         self.session = session_interface.open_session(
22             self.app, self.request
23         )
24 
25         if self.session is None:
26             self.session = session_interface.make_null_session(self.app)
flask.ctx.RequestContext.push

直接看 17 行: _request_ctx_stack.push(self) ,这里这个 self 指的就是当前保存了 request 和 session 信息的 ctx 对象。再进到这个 push 方法:

1 def push(self, obj):
2     rv = getattr(self._local, 'stack', None)
3     if rv is None:
4         self._local.stack = rv = []
5     rv.append(obj)
6     return rv
werkzeug.local.LocalStack.push

可以看到,从 self._local 中取一个名为 'stack' 的属性,如果为空,则创建一个空列表,并将 obj (这里也就是传入进来的 ctx )加入列表。再看一下这个 self._local :

 1 class Local(object):
 2     __slots__ = ('__storage__', '__ident_func__')
 3 
 4     def __init__(self):
 5         object.__setattr__(self, '__storage__', {})
 6         object.__setattr__(self, '__ident_func__', get_ident)
 7 
 8     def __iter__(self):
 9         return iter(self.__storage__.items())
10 
11     def __call__(self, proxy):
12         """Create a proxy for a name."""
13         return LocalProxy(self, proxy)
14 
15     def __release_local__(self):
16         self.__storage__.pop(self.__ident_func__(), None)
17 
18     def __getattr__(self, name):
19         try:
20             return self.__storage__[self.__ident_func__()][name]
21         except KeyError:
22             raise AttributeError(name)
23 
24     def __setattr__(self, name, value):
25         ident = self.__ident_func__()
26         storage = self.__storage__
27         try:
28             storage[ident][name] = value
29         except KeyError:
30             storage[ident] = {name: value}
31 
32     def __delattr__(self, name):
33         try:
34             del self.__storage__[self.__ident_func__()][name]
35         except KeyError:
36             raise AttributeError(name)
werkzeug.local.Local

查看发现这个 self._local 就是之前提到的 threading.local 中第三个版本。到这里一切明了,Flask 也是通过这种方式来完成线程之间的数据隔离,而 _request_ctx_stack 就是维护在 werkzeug.local.Local 对象中一个键为 'stack' 的列表,Flask 将 request 和 session 信息通过 flask.ctx.RequestContext 进行封装,再将其维护在这个列表中,这就是 Flask 的请求上下文管理。

app上下文

再回到 flask.ctx.RequestContext.push 中,回头看 8、9 行,查看 self.app.app_context 方法:

1 def app_context(self):
2     return AppContext(self)
flask.app.Flask.app_context

可以看到它返回一个 AppContext 实例,再看到 AppContext :

 1 class AppContext(object):
 2     def __init__(self, app):
 3         self.app = app
 4         self.url_adapter = app.create_url_adapter(None)
 5         self.g = app.app_ctx_globals_class()
 6 
 7         self._refcnt = 0
 8 
 9     def push(self):
10         self._refcnt += 1
11         if hasattr(sys, 'exc_clear'):
12             sys.exc_clear()
13         _app_ctx_stack.push(self)
14         appcontext_pushed.send(self.app)
15 
16     def pop(self, exc=_sentinel):
17         try:
18             self._refcnt -= 1
19             if self._refcnt <= 0:
20                 if exc is _sentinel:
21                     exc = sys.exc_info()[1]
22                 self.app.do_teardown_appcontext(exc)
23         finally:
24             rv = _app_ctx_stack.pop()
25         assert rv is self, 'Popped wrong app context.  (%r instead of %r)' \
26             % (rv, self)
27         appcontext_popped.send(self.app)
28 
29     def __enter__(self):
30         self.push()
31         return self
32 
33     def __exit__(self, exc_type, exc_value, tb):
34         self.pop(exc_value)
35 
36         if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
37             reraise(exc_type, exc_value, tb)
flask.ctx.AppContext

AppContext 对象中封装了 app 和一个 g 属性,最后把它赋值给 flask.ctx.RequestContext.push 中第 8 行的 app_ctx 变量,在第 9 行又执行了这个变量的 push 方法:

1 def push(self):
2     self._refcnt += 1
3     if hasattr(sys, 'exc_clear'):
4         sys.exc_clear()
5     _app_ctx_stack.push(self)
6     appcontext_pushed.send(self.app)
flask.ctx.AppContext.push

再执行第 5 行的 _app_ctx_stack.push(self) 方法,查看 _app_ctx_stack :

1 _request_ctx_stack = LocalStack()
2 _app_ctx_stack = LocalStack()
3 current_app = LocalProxy(_find_app)
4 request = LocalProxy(partial(_lookup_req_object, 'request'))
5 session = LocalProxy(partial(_lookup_req_object, 'session'))
6 g = LocalProxy(partial(_lookup_app_object, 'g'))
flask.globals

可以看到, _app_ctx_stack 和请求上下文中 _request_ctx_stack 一样,也是一个 LocalStack 对象,它的 push 方法也就对应上面的 werkzeug.local.LocalStack.push 块。所以这里也就是将封装了 app 和 g 的 flask.ctx.AppContext 实例 app_ctx 放入到 werkzeug.local.Local 管理的字典中。

LocalProxy

上面所说的都是上下文的存放原理,而我们实际使用时只需要在视图函数中导入响应对象即可。比如取 request.method :

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    method = request.method
    return 'index'

而这里使用的 request 正是上面 flask.globals 中的:

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

可以看到给 LocalProxy 类传入一个偏函数以实例化, request.method 实际上就是找 LocalProxy 类中的 method ,查看 LocalProxy 类:

  1 @implements_bool
  2 class LocalProxy(object):
  3     __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
  4 
  5     def __init__(self, local, name=None):
  6         object.__setattr__(self, '_LocalProxy__local', local)
  7         object.__setattr__(self, '__name__', name)
  8         if callable(local) and not hasattr(local, '__release_local__'):
  9             object.__setattr__(self, '__wrapped__', local)
 10 
 11     def _get_current_object(self):
 12         if not hasattr(self.__local, '__release_local__'):
 13             return self.__local()
 14         try:
 15             return getattr(self.__local, self.__name__)
 16         except AttributeError:
 17             raise RuntimeError('no object bound to %s' % self.__name__)
 18 
 19     @property
 20     def __dict__(self):
 21         try:
 22             return self._get_current_object().__dict__
 23         except RuntimeError:
 24             raise AttributeError('__dict__')
 25 
 26     def __repr__(self):
 27         try:
 28             obj = self._get_current_object()
 29         except RuntimeError:
 30             return '<%s unbound>' % self.__class__.__name__
 31         return repr(obj)
 32 
 33     def __bool__(self):
 34         try:
 35             return bool(self._get_current_object())
 36         except RuntimeError:
 37             return False
 38 
 39     def __unicode__(self):
 40         try:
 41             return unicode(self._get_current_object())  # noqa
 42         except RuntimeError:
 43             return repr(self)
 44 
 45     def __dir__(self):
 46         try:
 47             return dir(self._get_current_object())
 48         except RuntimeError:
 49             return []
 50 
 51     def __getattr__(self, name):
 52         if name == '__members__':
 53             return dir(self._get_current_object())
 54         return getattr(self._get_current_object(), name)
 55 
 56     def __setitem__(self, key, value):
 57         self._get_current_object()[key] = value
 58 
 59     def __delitem__(self, key):
 60         del self._get_current_object()[key]
 61 
 62     if PY2:
 63         __getslice__ = lambda x, i, j: x._get_current_object()[i:j]
 64 
 65         def __setslice__(self, i, j, seq):
 66             self._get_current_object()[i:j] = seq
 67 
 68         def __delslice__(self, i, j):
 69             del self._get_current_object()[i:j]
 70 
 71     __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
 72     __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
 73     __str__ = lambda x: str(x._get_current_object())
 74     __lt__ = lambda x, o: x._get_current_object() < o
 75     __le__ = lambda x, o: x._get_current_object() <= o
 76     __eq__ = lambda x, o: x._get_current_object() == o
 77     __ne__ = lambda x, o: x._get_current_object() != o
 78     __gt__ = lambda x, o: x._get_current_object() > o
 79     __ge__ = lambda x, o: x._get_current_object() >= o
 80     __cmp__ = lambda x, o: cmp(x._get_current_object(), o)  # noqa
 81     __hash__ = lambda x: hash(x._get_current_object())
 82     __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
 83     __len__ = lambda x: len(x._get_current_object())
 84     __getitem__ = lambda x, i: x._get_current_object()[i]
 85     __iter__ = lambda x: iter(x._get_current_object())
 86     __contains__ = lambda x, i: i in x._get_current_object()
 87     __add__ = lambda x, o: x._get_current_object() + o
 88     __sub__ = lambda x, o: x._get_current_object() - o
 89     __mul__ = lambda x, o: x._get_current_object() * o
 90     __floordiv__ = lambda x, o: x._get_current_object() // o
 91     __mod__ = lambda x, o: x._get_current_object() % o
 92     __divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
 93     __pow__ = lambda x, o: x._get_current_object() ** o
 94     __lshift__ = lambda x, o: x._get_current_object() << o
 95     __rshift__ = lambda x, o: x._get_current_object() >> o
 96     __and__ = lambda x, o: x._get_current_object() & o
 97     __xor__ = lambda x, o: x._get_current_object() ^ o
 98     __or__ = lambda x, o: x._get_current_object() | o
 99     __div__ = lambda x, o: x._get_current_object().__div__(o)
100     __truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
101     __neg__ = lambda x: -(x._get_current_object())
102     __pos__ = lambda x: +(x._get_current_object())
103     __abs__ = lambda x: abs(x._get_current_object())
104     __invert__ = lambda x: ~(x._get_current_object())
105     __complex__ = lambda x: complex(x._get_current_object())
106     __int__ = lambda x: int(x._get_current_object())
107     __long__ = lambda x: long(x._get_current_object())  # noqa
108     __float__ = lambda x: float(x._get_current_object())
109     __oct__ = lambda x: oct(x._get_current_object())
110     __hex__ = lambda x: hex(x._get_current_object())
111     __index__ = lambda x: x._get_current_object().__index__()
112     __coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
113     __enter__ = lambda x: x._get_current_object().__enter__()
114     __exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
115     __radd__ = lambda x, o: o + x._get_current_object()
116     __rsub__ = lambda x, o: o - x._get_current_object()
117     __rmul__ = lambda x, o: o * x._get_current_object()
118     __rdiv__ = lambda x, o: o / x._get_current_object()
119     if PY2:
120         __rtruediv__ = lambda x, o: x._get_current_object().__rtruediv__(o)
121     else:
122         __rtruediv__ = __rdiv__
123     __rfloordiv__ = lambda x, o: o // x._get_current_object()
124     __rmod__ = lambda x, o: o % x._get_current_object()
125     __rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
126     __copy__ = lambda x: copy.copy(x._get_current_object())
127     __deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
werkzeug.local.LocalProxy

可以看到这个类中并没有 method 属性,所以此时会执行 54 行也就是它的 __getattr__ 方法,它的返回值 self._get_current_object 中名为 name 变量值对应的属性,而此时这个 name 就等于 'method' ,我们此时就要看看 self._get_current_object 返回的是什么了。看 11-17 行,它的返回值是 self.__local() 的执行结果,而 self.__local 实际上就是上面传入的偏函数 partial(_lookup_req_object, 'request') ,查看这个偏函数:

1 def _lookup_req_object(name):
2     
3     top = _request_ctx_stack.top
4     if top is None:
5         raise RuntimeError(_request_ctx_err_msg)
6     return getattr(top, name)
flask.globals._lookup_req_object

此时执行这个偏函数, name 的值就为 'request' ,而 _request_ctx_stack.top 的返回值正是我们之前在 flask.app.Flask.wsgi_app 中看到的存入了封装了 request 和 session 的 ctx 变量,再从中取到名为 request 的属性返回,即 werkzeug.local.LocalProxy 中 54 行 self._get_current_object() 拿到的就是这个 request ,从中再取名为 method 的属性返回给视图函数,使用 session 的流程也是如此。取 app 上下文中内容也是如此,只不过它使用的上下文栈是 _app_ctx_stack 。

流程图

posted @ 2018-11-16 12:55  zze  阅读(225)  评论(0编辑  收藏  举报