python--local对象、flask上下文源码分析

一、local对象

背景:

多线成并发操作一个变量,会导致数据错乱,可以使用互斥锁加锁处理数据不安全的情况 (临界区)

解决:

使用local对象处理,多个线程操作的变量是local对象,就不会有并发安全的问题。因为它处理了并发安全的问题---->请求统一放在一个大字典中,key值是线程id号,value是个字典。

python 复制代码
# {111:{'name':jack},222:{'name':roma}}
l=local()
l.name='jack'---->l[111][name]
l.name='roma'---->l[222][name]

1.基本使用
不使用local,多线程并发操作,数据错乱

python 复制代码
import time
from threading import Thread
class Local():
    pass

l = Local()

def task(name):
    l.name = name 
    time.sleep(1)
    print('在线程内的名字是:', name, 'l对象中的名字大概率不一样', l.name)


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=task, args=['jack' + str(i) + '号', ])
        t.start() 

    # 等待所有线程都执行完成再执行下面代码
    time.sleep(6)
    print(l)

使用local

python 复制代码
import time
from threading import Thread
from threading import local

# 定义一个全局变量,并发安全的local,多个线程操作,不会错乱,因为每个线程用的都是自己的数据
l = local()

def task(name):
    l.name = name
    time.sleep(1)
    print('在线程内的名字是:', name, 'l对象中的名字也是', l.name)


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=task, args=['jack' + str(i) + '号', ])
        t.start()

    # 等待所有线程都执行完成再执行下面代码
    time.sleep(6)
    print(l)

2.自己写一个local类,线程和协程并发安全

通过字典自定义threading.local(函数)

python 复制代码
from threading import get_ident,Thread
import time
storage = {}
def set(k,v):
    ident = get_ident()  # 线程id号
    if ident in storage:
        storage[ident][k] = v
    else:
        storage[ident] = {k:v}
def get(k):
    ident = get_ident()
    return storage[ident][k]
def task(arg):
    set('val',arg) #
    v = get('val')
    print(v)


# 10个线程跑完,最终storage={123:{val:0},222:{val:1},333:{val:2},444:{val:3}.....}
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()

使用面向对象

python 复制代码
from threading import get_ident,Thread
import time
class Local(object):
    storage = {}
    def set(self, k, v):
        ident = get_ident()
        if ident in Local.storage:
            Local.storage[ident][k] = v
        else:
            Local.storage[ident] = {k: v}
    def get(self, k):
        ident = get_ident()
        return Local.storage[ident][k]
obj = Local()
def task(arg):
    obj.set('val',arg)
    v = obj.get('val')
    print(v)
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()

通过__setattr__和__getattr__方法实现

python 复制代码
from threading import get_ident,Thread
import time
class Local(object):
    storage = {}
    def __setattr__(self, k, v):
        ident = get_ident()
        if ident in Local.storage:
            Local.storage[ident][k] = v
        else:
            Local.storage[ident] = {k: v}
    def __getattr__(self, k):
        ident = get_ident()
        return Local.storage[ident][k]
obj = Local()
def task(arg):
    obj.val = arg
    print(obj.val)
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()

每个local对象用自己的存储空间(字典)

python 复制代码
from threading import get_ident, Thread
import time


class Local(object):
    def __init__(self):
        # self.storage={}  # 不能这样写 会递归
        object.__setattr__(self, 'storage', {})

    def __setattr__(self, k, v):
        ident = get_ident()  # 获取线程id号
        if ident in self.storage:
            self.storage[ident][k] = v
        else:
            self.storage[ident] = {k: v}    #

    def __getattr__(self, k):
        ident = get_ident()
        return self.storage[ident][k]


obj = Local()


def task(name):
    obj.name = name
    print(obj.name)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()

兼容线程和协程

python 复制代码
try:
    from greenlet import getcurrent as get_ident
except Exception as e:
    from threading import get_ident


from threading import Thread
import time
class Local(object):
    def __init__(self):
        object.__setattr__(self,'storage',{})
    def __setattr__(self, k, v):
        ident = get_ident()
        if ident in self.storage:
            self.storage[ident][k] = v
        else:
            self.storage[ident] = {k: v}
    def __getattr__(self, k):
        ident = get_ident()
        return self.storage[ident][k]
obj = Local()
def task(arg):
    obj.val = arg
    obj.xxx = arg
    print(obj.val)
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()

二、flask上下文源码分析

请求上下文执行流程(ctx):

python 复制代码
-0 flask项目一启动,有6个全局变量
	-_request_ctx_stack:LocalStack对象---->封装了local
	-_app_ctx_stack :LocalStack对象
	-request : LocalProxy对象
	-session : LocalProxy对象
	
-1 请求来了 app.__call__()---->内部执行:self.wsgi_app(environ, start_response)

-2 wsgi_app()
	-2.1 执行:ctx = self.request_context(environ):返回一个RequestContext对象,并且封装了request(当次请求的request对象),session
	-2.2 执行: ctx.push():RequestContext对象的push方法
		-2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
		-2.2.2 去_request_ctx_stack对象的类中找push方法(LocalStack中找push方法)
		-2.2.3 push方法源码:
		    def push(self, obj):
				#通过反射找self._local,在init实例化的时候生成的:self._local = Local()
				#Local()flask封装的支持线程和协程的local对象
				# 一开始取不到stack,返回None
				rv = getattr(self._local, "stack", None)
				if rv is None:
					#走到这,self._local.stack=[],rv=self._local.stack
					self._local.stack = rv = []
				# 把ctx放到了列表中
				#self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
				rv.append(obj)
				return rv
				
-3 如果在视图函数中使用request对象,比如:print(request)
	-3.1 会调用request对象的__str__方法,request类是:LocalProxy
	-3.2 LocalProxy中的__str__方法:lambda x: str(x._get_current_object())
		-3.2.1 内部执行self._get_current_object()
		-3.2.2 _get_current_object()方法的源码如下:
		    def _get_current_object(self):
				if not hasattr(self.__local, "__release_local__"):
					#self.__local()  在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
					# 用了隐藏属性
					#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
					#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
					#这个地方的返回值就是request对象(当此请求的request,没有乱)
					return self.__local()
				try:
					return getattr(self.__local, self.__name__)
				except AttributeError:
					raise RuntimeError("no object bound to %s" % self.__name__)
		-3.2.3 _lookup_req_object函数源码如下:
			def _lookup_req_object(name):
				#name是'request'字符串
				#top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
				top = _request_ctx_stack.top
				if top is None:
					raise RuntimeError(_request_ctx_err_msg)
				#通过反射,去ctx中把request对象返回
				return getattr(top, name)
		-3.2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__
		
-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性

-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉

其他的东西:

python 复制代码
-session:
	-请求来了opensession
		-ctx.push()---->也就是RequestContext类的push方法的最后的地方:
			if self.session is None:
				#self是ctx,ctx中有个app就是flask对象,   self.app.session_interface也就是它:SecureCookieSessionInterface()
				session_interface = self.app.session_interface
				self.session = session_interface.open_session(self.app, self.request)
				if self.session is None:
					#经过上面还是None的话,生成了个空session
					self.session = session_interface.make_null_session(self.app)
					
	-请求走了savesession
		-response = self.full_dispatch_request() 方法内部:执行了before_first_request,before_request,视图函数,after_request,savesession
		-self.full_dispatch_request()---->执行:self.finalize_request(rv)-----》self.process_response(response)----》最后:self.session_interface.save_session(self, ctx.session, response)
		
-请求扩展相关
	before_first_request,before_request,after_request依次执行
	
-信号的触发
	信号名.send()
	
-flask有一个请求上下文,一个应用上下文
	-ctx:
		-是:RequestContext对象:封装了request和session
		-调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置
	-app_ctx:
		-是:AppContext(self) 对象:封装了当前的app和g
		-调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
		
-g是个什么鬼?
	专门用来存储用户信息的g对象,g的全称的为global 
	g对象在一次请求中的所有的代码的地方,都是可以使用的(当次请求中传递一些数据)
	
-代理模式
	-request和session就是代理对象,用的就是代理模式

-g对象和session的区别
	g对象只对当次请求有效(当此请求内有效)
    session:可以跨请求,该用户的多次请求中都可以使用

总结:

1 flask中间件,使用请求扩展,完成django中间件的功能

2 快速生成当前项目的依赖(两种方案)

  • pipreqs

3 函数和方法的区别

  • 面向对象中有方法的概念
  • 绑定给对象的,绑定给类的方法
  • 特殊之处是自动传值
  • 方法有可能是函数(对象的绑定方法,如果类来调用,就是函数)

4 偏函数 partial

  • 提前给函数传值

5 local对象

  • 解决并发安全的问题
  • 多条线程操作同一个变量,会出现数据安全问题,解决该问题,需要加锁
  • 每条线程操作的都是自己线程的数据
  • threading包下的local类---->实例化得到对象---->多线程并发操作---->数据不会错乱

6 自定义local对象

python 复制代码
self.name='lqz'  # 会触发__setattr__---->会出现递归
setattr(self,'name','lqz')  # 会出现递归
object.__setattr__(self,'name','lqz')  # 不会出现递归
self.__dict__()  # 属性字典,也不会出现递归

7 flask请求上下文:RequestContext---->ctx:request,session,flash

8 flask应用上下文:AppContext---->app_ctx:当前app,g

9 ctx对象:请求上下文,flask整个请求的流程

python 复制代码
请求来了---->app()---->触发Flask类的__call__方法---->app.wsgi_app()

ctx = self.request_context(environ) # ctx中包含当此请求的request,session,把ctx放到了local对象中,来一个请求就放一次,local处理了并发安全,所以自己放的都是自己的,相互不影响

response = self.full_dispatch_request()# 执行请求扩展,执行视图函数或者视图类,处理了session,还有信号

不管在整个过程中是否出异常,ctx都从local对象上移除

10 flask请求上下文源码分析

python 复制代码
1 请求来了执行---->app()---->触发类的__call__---->self.wsgi_app(environ, start_response)--->

2 ctx = self.request_context(environ)---->封装了request和session

3 ctx.push()---->_request_ctx_stack.push(self)---->self是ctx

4 _request_ctx_stack是LocalStack类的对象---->push
5 LocalStack的push方法源码
    def push(self, obj):
        rv = getattr(self._local, "stack", None)
        if rv is None:
            self._local.stack = rv = []
            # self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
        rv.append(obj)
        return rv
        
6 LocalStack对像中的  _local--->init初始化出来的
self._local = Local() # 咱们自己写的可以多线程并发访问的Local

7 local={线程或协程id号:{stack:[ctx]},线程或协程id号:{stack:[ctx]}}
local.stack---->取stack的值,在不同协程下,取到的是自己的

8 在视图函数中使用request,session---->都是当此请求的request和session,但是我们使用了全局变量。打印的真的是当次请求的Request类的对象,但实际上request根本不是Request类的对象,LocalProxy类的对象,LocalProxy类重写了__str__

9 print(request)的时候---->类的__str__

10 LocalProxy把所有的魔法方法都重写了,因为它是个代理类

11 request = LocalProxy(partial(_lookup_req_object, "request"))
init---->def __init__(self, local, name=None):---->object.__setattr__(self, "_LocalProxy__local", local)--->local就是偏函数

12 LocalProxy---》__str__--->_get_current_object()是LocalProxy类的方法

13 LocalProxy._get_current_object()---》return self.__local()--》加括号执行偏函数---》partial(_lookup_req_object, "request")()--->_lookup_req_object('request')

14 返回了当前线程所在的ctx中的request对象

15 request.method-->就是当前线程的request对象的method方法

16 在视图函数中打印print(session)--->是当此请求的session

# 应用上下文
# g到底是什么,是一个全局变量,放和取在当次请求中的数据

# session:open_session   save_session

# 信号的触发位置
# 请求扩展中三个:的执行位置
相关推荐
海绵波波1071 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
0wioiw06 小时前
Flask-----SQLAlchemy教程
后端·python·flask
hnmpf1 天前
flask-admin modelview 中重写get_query函数
后端·python·flask
程序员黄同学3 天前
如何使用 Flask 框架创建简单的 Web 应用?
前端·python·flask
hnmpf4 天前
flask-admin+Flask-WTF 实现实现增删改查
后端·python·flask
hnmpf4 天前
flask-admin的modelview 实现list列表视图中某个列字段值翻译
后端·python·flask
jjw_zyfx4 天前
flask before_request 请求拦截器返回无值则放行,有值则拦截
后端·python·flask
盼兮*4 天前
Python调用星火认知大模型API(流式传输)
开发语言·python·flask·request
hnmpf4 天前
wtforms+flask_sqlalchemy在flask-admin视图下实现日期的修改与更新
后端·python·flask
jjw_zyfx5 天前
flask flask-socketio创建一个网页聊天应用
后端·python·flask