flask请求上下文源码解析
预热
面向对象
class Local(object):
def __init__(self):
self.dict = {}
def __setattr__(self, key, value):
self.dict[key] = value
obj = Local()
这样写会报错
如果自定义了自己的__setattr__,在初始化的时候就不能这样给对象绑定属性,因为使用self.dict = {}的时候就会去调用__setattr__去给对象设置属性,但是此时对象还没有一个dict的属性。但凡是给自己写的
这个类实例化的对象,调用对象的obj.xx = oo 这种方式都会触发__setattr__的执行,但是如果在这个对象里操作一个字典对象(或其他对象),对这个字典对象(或其他对象)的操作就不会有问题,因为就算其他对象的.
方式
是调用其他对象自己的__setattr__,和我们写的类没有关系
ThreadLocal
from threading import get_ident
from threading import Thread
import time
class Local(object):
pass
obj = Local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
得到的结果如下
这是因为在python中的多线程中,对同一个全局变量的修改会有竞争问题,此时全局变量obj最终存的值是最后一个线程赋的值。要想实现每个线程的数据隔离,单独的线程打印单独线程传入的变量,可以在每个线程维护相对应的局部变量,代码如下
from threading import get_ident
from threading import Thread
import time
class Local(object):
pass
obj = Local()
def task(i):
dic = {'i':i}
time.sleep(1)
print(get_ident(),':',dic['i'])
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
此时的结果为
但是python中还提供了一个叫做线程局部变量的玩意,叫local.
from threading import local
from threading import get_ident
from threading import Thread
import time
obj = local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
此时的结果就是
从上面的结果不难看出,为什么local叫线程局部变量,因为local对象明明是定义在全局的,但是却有类似于在线程创建相应的局部变量的效果。
从使用效果上来看,这个叫做线程局部变量的玩意好牛逼,能让我们以很简单类似于平常使用类的方式做到了数据隔离。是的,乍一看很牛逼,说白了就是别人在内部做了一些事,封装的好。那么内部做了些啥事呢,可以这样先简单地去理解:这个对象内部维护一个大字典,字典的key就是线程的唯一标识,value也是一个字典,这个字典存线程的数据。通过这个想法,自定义一个支持协程的隔离数据的类
try:
from gevent import getcurrent as get_ident
except ImportError:
from threading import get_ident
from threading import Thread
import time
class Local(object):
def __init__(self):
object.__setattr__(self,'__storage__', {})
def __setattr__(self, key, value):
thread_id = get_ident()
try:
self.__storage__[thread_id][key] = value
except KeyError as e:
self.__storage__[thread_id] = {key: value}
def __getattr__(self, item):
# __getattr__ 是在obj.属性的时候被调用,而且这个属性必须不是对象本身的属性
# 这里__setattr__ 并不是把属性绑定到对象,而是放到字典里,所以即使obj.x = 1,访问obj.x 还是会调用__getattr__
thread_id = get_ident()
try:
return self.__storage__[thread_id][item]
except KeyError:
return None
obj = Local()
def task(i):
obj.x = i
time.sleep(1)
print(get_ident(),':',obj.x)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
LocalStack
有了Local,那么我们就可以对这个对象进行操作了。
obj = Local()
obj.stack = ''xxx"
print(obj.__storage__)
这里可以给小字典的value设置为字符串,也可以设置为其他数据结果,比如列表
obj = Local()
obj.stack = []
obj.stack.append(1)
obj.stack.pop()
print(obj.__storage__)
我们发现,对这个列表的操作只用到了append和pop,相当于维护成一个堆栈,这里我们需要每次都obj.stack.push(),感觉有点麻烦,能不能直接push就能操作Local中的数据呢?那就需要找另一个“代理”(封装一个新的类)。
所以,为了对数据列表操作更方便,这里封装一个LocalStack的类
class LocalStack(object):
def __init__(self):
self._local = Local()
def push(self, obj):
rv = getattr(self._local, 'stack', None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
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()
obj = LocalStack()
obj.push(1)
obj.pop()
print(obj._local.__storage__)
偏函数
from functools import partial
def task(name, age):
print(name, age)
func = partial(task, 'jack')
func(12) # 解果是jack 12
偏函数就是帮我们传参数,下次调用就不需要再次传参
LocalProxy
# 用DATA这个大字典模拟ctx,ctx中含有request和session, request中封装了method等
DATA = {
'request':{
'method':"GET",
'form':{}
},
'session':{
'user':'alex',
'age':"19"
}
}
class LocalProxy(object):
def __init__(self,key):
self.key = key
def get_dict(self):
return DATA[self.key]
def __getattr__(self, item):
data_dict = self.get_dict()
return data_dict[item]
def __getitem__(self, item):
data_dict = self.get_dict()
return data_dict[item]
request = LocalProxy('request')
session = LocalProxy('session')
print(request.method)
print(session.user)
LocalProxy 这个类就是方便以非常简单的方式去大字典或者对象中取数据,当然,没有这个类我们也可以自己去那个大字典或者对象中取取我们想要的内容,这里只不过是做了面向对象的封装,而不是封装成一个函数
源码简易流程
from flask import Flask,request,session
app = Flask(__name__)
@app.route('/index')
def index():
# 1. request是LocalProxy对象
# 2. 对象中有method、执行__getattr__
print(request.method)
# request['method']
# 1. session是LocalProxy对象
# 2. LocalProxy对象的__setitem__
session['x'] = 123
return "Index"
if __name__ == '__main__':
app.run()
# app.__call__
# app.wsgi_app
"""
第一阶段:请求到来
将request和Session相关数据封装到ctx=RequestContext对象中。
再通过LocalStack将ctx添加到Local中。
__storage__ = {
1231:{'stack':[ctx(request,session)]}
}
第二阶段:视图函数中获取request或session
方式一:直接找LocalStack获取
from flask.globals import _request_ctx_stack
print(_request_ctx_stack.top.request.method)
方式二:通过代理LocalProxy获取,其实在LocalProxy中肯定也是先要去把request对象取到,然后在帮忙通过反射去取值
from flask import Flask,request
print(request.method)
"""
源码详细