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

# 信号的触发位置
# 请求扩展中三个:的执行位置
相关推荐
weixin_BYSJ19872 天前
springboot医疗信息管理系统---附源码17465
java·javascript·spring boot·python·django·flask·php
运维行者_3 天前
网络监控与ITSM集成:从告警到工单,实现运维自动化闭环
开发语言·网络·分布式·后端·架构·flask·php
way_hj3 天前
Flask笔记(2)快速web项目
笔记·flask·vue·web
海盗12343 天前
微软技术日报——2026-08-07
python·microsoft·flask
weixin_BYSJ19873 天前
【java项目分享】springboot阅读推荐平台10600
java·javascript·spring boot·python·django·flask·php
Vec‑Jie4 天前
MerchantOps-KBQA 实践(十四):本地运行、性能限制与可验证的交付边界
人工智能·后端·python·flask
mqiqe6 天前
LLM Gateway:企业级大模型应用的“统一控制平面”
平面·flask·gateway
aGdF8E3gQ6 天前
【Application Insights】采样率对Function App日志收集的影响和解决方法
python·flask·wpf
编码者卢布6 天前
【Azure APIM】通过 API Management 公开API为 MCP Server 的试验 (二)
microsoft·flask·azure
编码者卢布7 天前
【Azure APIM】通过 API Management 公开现有 MCP Server 的试验 (一)
microsoft·flask·azure