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

知識儲備

偏函數

  • 做用

    偏函數,幫助開發者自動傳遞參數。html

  • 使用

    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__ 屬性的結果順序來的python

面向對象的特殊方法

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

  • 做用

    爲每一個線程建立一個獨立的空間,使得線程對本身空間中的數據進行操做(數據隔離)。flask

  • 示例

    看以下示例:cookie

    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 其實也能夠爲咱們解決這種問題,以下:session

    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
    '''
    例:

    能夠看到並無發生值覆蓋的問題。多線程

  • 原理

    修改上述第一個示例以下:app

    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
    '''
    例:初版

    其實原理很簡單,一個字典,經過獲取正執行線程的惟一標記做爲鍵,經過這個鍵就可以區分開線程隔離保存數據。ide

    同理,按上述原理,咱們也能夠將其改成根據協程隔離數據:函數

    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 中,回頭看 八、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 。

流程圖

相關文章
相關標籤/搜索