# 上下文管理准备工作
Flask的request和session设置方式比较新颖(直接import),如果没有这种方式,那么我们的每个视图函数都将把request参数传入,但实际上我们写的视图函数都没有传递这些参数,那么Flask是如何做到的哪?
# threading.local()
在讲上下文之前我们必须重新回顾下线程安全问题。
# 线程实例
import threading class Foo(object): def __init__(self): self.name = 0 local_values = Foo() # 实例化对象 def func(num): local_values.name = num # 给对象的属性赋值 import time time.sleep(1) #每个线程睡1秒 print(local_values.name, threading.current_thread().name) #最后打印线程的name属性 for i in range(20): th = threading.Thread(target=func, args=(i,), name='线程%s' % i) th.start() #运行结果: 19 线程0 19 线程2 19 线程3 19 线程1 19 线程7....
# 分析
1、多个线程操作同一个变量,以最后操作后的变量为准 2、可以通过:加锁来确保每个线程操作完成,但是和同步无区别效果
# 使用threading.local类解决线程不安全问题
import threading # local_values = threading.local() # 和下面继承的效果一样,只是下面是一个派生类实现 class Foo(threading.local): def __init__(self): self.name = 0 local_values = Foo() # 实例化对象 def func(num): local_values.name = num # 给对象的属性赋值 import time time.sleep(1) #每个线程睡1秒 print(local_values.name, threading.current_thread().name) #最后打印线程的name属性 for i in range(20): th = threading.Thread(target=func, args=(i,), name='线程%s' % i) th.start() #运行结果: 1 线程1 0 线程0 2 线程2 3 线程3 4 线程4....
# 分析:
1、threading.local对象,用于为每一个线程开辟一块空间保存它独有的值 { '线程1的id':{'name':1}, '线程2的id':{'name':2}, ... } 2、local解决线程不安全的方法:通过消耗内存,将每个线程创建的独有数据进行保存
# 源码request
**情景一:**单进程单线程 ----> 基于全局变量没问题(import request) --> 慢
问题描述:
一个请求 request 进来,通过 import 导入在试图函数中进行使用,最后走的时候将 request 清空,下一个请求再进来,就像排队上厕所样...
情景二:单进程多线程 ----> 通过 threading.local 对象解决 --> 占资源
问题描述
此时多个请求request同时进入,而 import request 是全局导入的,A使用 request 的时候,B也在使用 request 此时就可能发生资源竞争问题,使 request 的数据混乱
情景三:单进程单线程(多协程) ----> 通过 threading.local 对象不能解决(一个线程共享资源)
# 以上三种情景分析
如果不支持协程,以便可以使用threading.local,但是这恰恰违背了Flask的轻快的原则,所以Flask想必须使用到协程,但是有保证安全,于是Flask自定义规则。类似于threading.local一样(支持协程) 也就是说,当我们要支持协程的时候,单纯的线程数据隔离已经不满足要求了, 我们需要对协程进行数据隔离,因为线程id重复,数据跟着重复。。。
# 自定义安全单线程
# 获得线程id
import threading from _thread import get_ident def task(arg): v = get_ident() print(v) for i in range(20): th = threading.Thread(target=task,args=(i,)) th.start() # 运行结果: 8012 2744 8632 3940...
# 自己构造类似的Local
import threading from _thread import get_ident # 获取当前线程的id class Local(object): def __init__(self): self.storage = {} # 用于存储每个线程相应的数据 self.get_ident = get_ident # 调用获取进程的方法 def set(self, k, v): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id if not origin: origin = {k: v} else: origin[k] = v self.storage[ident] = origin # 保存数据 def get(self, k): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id print(self.storage) if not origin: return None return origin.get(k, None) local_values = Local() def task(num): local_values.set('name',num) import time time.sleep(1) print(local_values.get('name'), threading.current_thread().name) for i in range(20): th = threading.Thread(target=task, args=(i,),name='线程%s'%i) th.start() -------------------------------- #运行结果: 1 线程1 0 线程0 2 线程2 3 线程3... #构造的storage: { 10164: {'name': 0}, #线程id:{数据} 12328: {'name': 1}, 11084: {'name': 2}, ... }
上面的方法同样不支持协程,怎么样才能支持协程?
# 协程版本的 Local
import threading try: from greenlet import getcurrent as get_ident # 获取当前协程的id,支持协程 except ImportError: try: from thread import get_ident # 没有协程就从这导入 except ImportError: from _thread import get_ident # 获取当前线程的id,支持线程 class Local(object): def __init__(self): self.storage = {} # 用于存储每个线程相应的数据 self.get_ident = get_ident # 调用获取进程的方法 def set(self, k, v): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id if not origin: origin = {k: v} else: origin[k] = v self.storage[ident] = origin # 保存数据 def get(self, k): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id print(self.storage) if not origin: return None return origin.get(k, None) local_values = Local() def task(num): local_values.set('name',num) import time time.sleep(1) print(local_values.get('name'), threading.current_thread().name) for i in range(20): th = threading.Thread(target=task, args=(i,),name='线程%s'%i) th.start() # 运行结果: 1 线程1 0 线程0 2 线程2... # 相应的storage: { <greenlet.greenlet object at 0x000002F009DB0340>: {'name': 0}, <greenlet.greenlet object at 0x000002F009DB03D8>: {'name': 1}, <greenlet.greenlet object at 0x000002F009DB0470>: {'name': 2}, .... }
# 使用最小执行单位协程,使id不会重复,从而达到数据不重复!!
# 使用面向对象的方式优化上面代码(Flask的session和rquest都是基于下面的代码实现)
import threading try: from greenlet import getcurrent as get_ident # 获取当前协程的id,支持协程 except ImportError: try: from thread import get_ident # 没有协程就从这导入 except ImportError: from _thread import get_ident # 获取当前线程的id,支持线程 class Local(object): def __init__(self): object.__setattr__(self,'storage',{}) # 可以通过父类的方法来进行赋值 object.__setattr__(self, 'get_ident', get_ident) # self.storage = {} # 这样初始化会报错,因为会递归调用__setattr__方法 # self.get_ident = get_ident def __setattr__(self, k, v): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id if not origin: origin = {k: v} else: origin[k] = v self.storage[ident] = origin # 保存数据 def __getattr__(self, item): ident = self.get_ident() # 获取线程id origin = self.storage.get(ident) # 查找总存储中是否有该id print(self.storage) if not origin: return None return origin.get(item, None) local_values = Local() def task(num): local_values.name = num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(3): th = threading.Thread(target=task, args=(i,),name='线程%s'%i) th.start()
#
__setattr__
设置值时候的误区
# 偏函数
固定函数中的一些参数,返回一个新的函数,方便调用
# 简单演示
import functools def func(a1,a2): #func1必须同时传入两个参数才能运行 print(a1,a2) new_func = functools.partial(func,666) # partial会为我们生成一个新函数,并且将func函数中的第一个参数赋值为666 new_func(99) # 666 99 def func1(a,b,c): print(a,b,c) x1 = functools.partial(func1,1) # 类似的演示 x2 = functools.partial(x1,2) x2(3) # 1 2 3 ---------------------------------------- def func2(a,b,c): print(a,b,c) x1 = functools.partial(func2,1,2) # 可同时传递多个参数 x1(3) # 1 2 3 ---------------------------------------- def func3(a,b,c): print(a,b,c) x1 = functools.partial(func3,c=3,a=1) # 使用关键字传参 x1(b=2) # 1 2 3
# 类中的内置方法
__add__
方法class Foo(object): def __init__(self,num): self.num = num def __add__(self, other): data = self.num + other.num return Foo(data) obj1 = Foo(1) obj2 = Foo(2) v = obj1 + obj2 print(v.num)
查看Flask中的所有类的内置方法
import flask.globals --> LocalProxy __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v) __delattr__ = lambda x, n: delattr(x._get_current_object(), n) __str__ = lambda x: str(x._get_current_object()) __lt__ = lambda x, o: x._get_current_object() < o __le__ = lambda x, o: x._get_current_object() <= o __eq__ = lambda x, o: x._get_current_object() == o __ne__ = lambda x, o: x._get_current_object() != o __gt__ = lambda x, o: x._get_current_object() > o __ge__ = lambda x, o: x._get_current_object() >= o __cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa __hash__ = lambda x: hash(x._get_current_object()) __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw) __len__ = lambda x: len(x._get_current_object()) __getitem__ = lambda x, i: x._get_current_object()[i] ....
**链条函数:**将所有的函数排序执行
from itertools import chain def f1(x): return x + 1 func_list = [f1,lambda x:x-1] def f2(x): return x+10 new_func_list = chain([f2,],func_list) for func in new_func_list: print(func) #<function f2 at 0x00000236385E1AE8> #<function f1 at 0x0000023638401E18> #<function <lambda> at 0x00000236385E1A60>