1. 計算機科學領域的任何問題均可以經過增長一個間接的中間層來解決, 上下文機制就是這句話的體現。html
2. 若是一次封裝解決不了問題,那就再來一次python
上下文:至關於一個容器,保存了Flask程序運行過程當中的一些信息 源碼:flask/ctx.pygit
請求上下文:Flask從客戶端收到請求時,要讓視圖函數能訪問一些對象,這樣才能處理請求,要想讓視圖函數可以訪問請求對象,一個顯而易見的方式是將其做爲參數傳入視圖函數,不過這會致使程序中的每一個視圖函數都增長一個參數,除了訪問請求對象,若是視圖函數在處理請求時還要訪問其餘對象,狀況會變得更糟。爲了不大量無關緊要的參數把視圖函數弄得一團糟,Flask使用上下文臨時把某些對象變爲全局可訪問。這就是一種重構設計思路。github
request.args.get('user')
session['name'] = user.id
應用上下文:應用程序上下文,用於存儲應用程序中的變量web
# context locals # 使用代理模式 LocalProxy _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session')) g = LocalProxy(partial(_lookup_app_object, 'g'))
#!/usr/bin/python3 # -*- coding: utf-8 -*- # __author__ = '__JonPan__' from flask import Flask, current_app app = Flask(__name__) a = current_app # is_debug = current_app.config['DEBUG'] @app.route('/') def index(): return '<h1>Hello World. Have a nice day! </1>' if __name__ == '__main__': app.run(host='localhost', port=8888)
報錯:sql
Exception has occurred: RuntimeError數據庫
Working outside of application context.編程
flask上下文對象出入棧模型圖flask
在應用開發中可用直接引用current_app
不會報錯,是由於當在一個請求中使用的時候,flask會判斷_app_ctx_stack
棧頂是否有可用對象,若是沒有就會自動推入一個App. 咱們獲取的current_app
就是獲取的棧頂元素設計模式
# flask/globals.py def _find_app(): top = _app_ctx_stack.top if top is None: raise RuntimeError(_app_ctx_err_msg) return top.app current_app = LocalProxy(_find_app)
修改代碼:將app對象推入棧頂
# ... # 將app_context 推入棧中 ctx = app.app_context() ctx.push() a = current_app is_debug = current_app.config['DEBUG'] ctx.pop() # ... # 更有pythonnic的寫法 # 將app_context 推入棧中 # with app.app_context(): # a = current_app # is_debug = current_app.config['DEBUG']
不在出現unbound
狀態。可正常運行
既然flask會自動幫咱們檢測棧頂元素是否存在,爲何咱們還要作這一步操做,當咱們在寫離線應用,或者單元測試的時候就須要用到,由於請求是模擬的,不是在application context中的了。
實現了__enter__
和__exit__
方法的對象就是一個上文管理器。
__enter__
和__exit__
方法的對象就是一個上文管理器。class MyResource: def __enter__(self): print('connect ro resource') return self def __exit__(self, exc_type, exc_value, tb): if tb: print('process exception') else: print('no exception') print('close resource connection') # return True # return False def query(self): print('query data') try: with MyResource() as r: 1/0 r.query() except Exception as e: print(e)
with MyResour ce() as r
as 的別名r指向的不是上想問管理器對象,而是__enter__
方法返回的值,在以上代碼確實是返回了對象自己。
__exit__
方法 處理退出上下文管理器對象時的一些資源清理工做,並處理異常,三個參數
__exit__
實際上是有返回值的,return True
表示,異常信息已經在本方法中處理,外部可不接收異常,return False
表示將異常拋出給上層邏輯處理,默認不寫返回,即默認值是None
, None
也表示False
contextmanager
裝飾器使用 contextmanager
的裝飾器,能夠簡化上下文管理器的實現方式。原理是經過 yield
將函數分割成兩部分,yield
以前的語句在__enter__
方法中執行,yield
以後的語句在__exit__
方法中執行。緊跟在 yield
後面的值是函數的返回值。
from contextlib import contextmanager class MyResource: def query(self): print('query data') @contextmanager def my_resource(): print('connect ro resource') yield MyResource() print('close resource connection') try: with my_resource() as r: r.query() except Exception as e: print(e)
兩種方法並無說哪種方法好,各有優略,看代碼環境的使用場景來決定。contextmanager
如下就是一個簡單的需求,給文字加書名號。
@contextmanager def book_mark(): print('《', end='') yield print('》', end='') with book_mark(): print('你還年輕? peer已經年少有爲!', end='')
實際應用場景,封裝一些公用方法。
本來業務邏輯中是這樣的, 在全部的模型類中,在新建資源的時候都須要add
, commit
, 或者rollback
的操做。
@login_required def save_to_gifts(isbn): if current_user.can_save_to_list(isbn): try: gift = Gift() gift.isbn = isbn gift.uid = current_user.id db.session.add(gift) db.session.commit() except Exception as e: db.session.rollback() raise e
簡化後
@login_required def save_to_gifts(isbn): if current_user.can_save_to_list(isbn): with db.auto_commit(): gift = Gift() gift.isbn = isbn gift.uid = current_user.id db.session.add(gift)
其中數據庫的封裝以下
#!/usr/bin/python3 # -*- coding: utf-8 -*- # __author__ = '__JonPan__' from contextlib import contextmanager from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy, BaseQuery # 抽離公用代碼,with 形式來完成db.commit rollback class SQLAlchemy(_SQLAlchemy): @contextmanager def auto_commit(self): try: yield self.session.commit() except Exception as e: self.session.rollback() raise e class Query(BaseQuery): def filter_by(self, **kwargs): if 'status' not in kwargs.keys(): kwargs['status'] = 1 return super(Query, self).filter_by(**kwargs) db = SQLAlchemy(query_class=Query)
如何實現一個Reqeust 指向多個請求實例,且要區分該實例對象所綁定的用戶?
字典:
request = {'key1': val1, 'key2': val2}
flask引用 werkzeug
中的 local.Local
實現線程隔離
# werkzeug\local.py 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): # 獲取當前線程的id 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)
能夠看到是使用線程ID來綁定不一樣的上線文對象。
class LocalStack(object): """This class works similar to a :class:`Local` but keeps a stack of objects instead. This is best explained with an example:: >>> ls = LocalStack() >>> ls.push(42) >>> ls.top 42 >>> ls.push(23) >>> ls.top 23 >>> ls.pop() 23 >>> ls.top 42 They can be force released by using a :class:`LocalManager` or with the :func:`release_local` function but the correct way is to pop the item from the stack after using. When the stack is empty it will no longer be bound to the current context (and as such released). By calling the stack without arguments it returns a proxy that resolves to the topmost item on the stack. .. versionadded:: 0.6.1 """ 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): """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
Local使用字典的方式實現線程隔離,LocalStack 則封裝Local實現了線程隔離的棧結構
current_app
, requsts
, 等代理對象都是線程隔離的, 當咱們啓動一個線程去執行一個異步操做須要用到應用上下文時(需傳入app
對象), 若是傳入current_app
, 此時的app
時 unbond
的狀態, 因爲線程id改變了, 因此在新的線程中 全部的棧都是空的, 可是在整個web
中 由Flask 實例化的 app
是惟一的, 因此獲取app傳入是能夠的 app = current_app._get_current_object()
current_app -> (LocalStack.top = AppContext top.app = Flask)
request -> (LocalStack.top = RequestContext.top.request = Request)