# 上下文管理准备工作

	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,12)  # 可同时传递多个参数
    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>