偏函數,幫助開發者自動傳遞參數。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)
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())
爲每一個線程建立一個獨立的空間,使得線程對本身空間中的數據進行操做(數據隔離)。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 '''
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())
以前說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)
一個網站程序是可能有多個用戶也就是多個線程同時訪問的,之因此上面會提 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)
直接看 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
能夠看到,從 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)
查看發現這個 self._local 就是以前提到的 threading.local 中第三個版本。到這裏一切明瞭,Flask 也是經過這種方式來完成線程之間的數據隔離,而 _request_ctx_stack 就是維護在 werkzeug.local.Local 對象中一個鍵爲 'stack' 的列表,Flask 將 request 和 session 信息經過 flask.ctx.RequestContext 進行封裝,再將其維護在這個列表中,這就是 Flask 的請求上下文管理。
再回到 flask.ctx.RequestContext.push 中,回頭看 八、9 行,查看 self.app.app_context 方法:
1 def app_context(self): 2 return AppContext(self)
能夠看到它返回一個 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)
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)
再執行第 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'))
能夠看到, _app_ctx_stack 和請求上下文中 _request_ctx_stack 同樣,也是一個 LocalStack 對象,它的 push 方法也就對應上面的 werkzeug.local.LocalStack.push 塊。因此這裏也就是將封裝了 app 和 g 的 flask.ctx.AppContext 實例 app_ctx 放入到 werkzeug.local.Local 管理的字典中。
上面所說的都是上下文的存放原理,而咱們實際使用時只須要在視圖函數中導入響應對象便可。好比取 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)
能夠看到這個類中並無 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)
此時執行這個偏函數, 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 。