知识储备
偏函数
-
作用
偏函数,帮助开发者自动传递参数。
-
使用
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)
一个网站程序是可能有多个用户也就是多个线程同时访问的,之所以上面会提 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 的请求上下文管理。
app上下文
再回到 flask.ctx.RequestContext.push 中,回头看 8、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 管理的字典中。
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)
可以看到这个类中并没有 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 。