Flask源码学习【个人自学记录】-上下文管理

前情知识点

python的魔术方法

这里只讨论下述内容会用到的魔术方法

  1. __init__(self)初始化方法:
触发机制:实例化对象之后立即触发
参数:至少有一个self,接收当前对象,其他参数根据需要进行定义
返回值:无
作用:初始化对象的成员
  1. __call__
调用对象的魔术方法
触发时机:将对象当作函数调用时触发,方式: 对象()
参数:至少一个self接收对象,其余根据调用时参数决定
返回值:根据情况而定
作用:可以将复杂的步骤进行合并操作,减少调用的步骤,方便使用
注意:无
  1. __setattr__(self,key,value)
触发时机:设置对象成员值的时候触发
参数:1个当前对象的self,一个是要设置的成员名称字符串,一个是要设置的值
返回值:无 过程操作
作用:接管设置操作,可以在设置前之前进行判断验证等行为
注意:在当前方法中无法使用成员=值的方式直接设置成员,否则会无限递归,必须借助object的设置方法来完成

object.__setattr__(参数1,参数2,参数3)
  1. __getattr__(item)
触发时机:获取不存在的对象成员时触发
参数:一个是接收当前对象的self,一个是获取成员名称的字符串
返回值:必须有值
作用:为访问不存在的属性设置值
注意:getattribute无论何时都会在getattr之前触发,触发了getattribute就不会在触发getattr了
  1. __delattr__
触发时机:删除对象成员时触发
参数:一个当前对象的self
返回值:无
作用:可以在删除成员时进行验证。

对象取值时,取值的顺序为:先从__getattribute__中找,第二步从对象的属性中找,第三步从当前类中找,第四步从父类中找,第五步从__getattr__中找,如果没有,直接抛出异常。

线程的唯一标识

通过threading.get_ident()获取线程的唯一标识

import threading

def task():
    ident = threading.get_ident()
    print(ident)

for i in range(4):
    t = threading.Thread(target=task)
    t.start()

threading.local

在主线程中实例化一个 local 类的对象后,在不同的线程中使用这个对象存储的数据,其它线程不可见,即:各个线程中,该对象的值互不干扰。【为各个线程开辟一份内存,存储local对象中的值】

  • 在不同的线程中,修改普通的类对象的属性
import threaring
import time

# Foo类--普通的类
class Foo:
	def __init__(self): self.num=0

v = Foo()			# 实例化Foo类的对象
def task(arg):
	v.num = arg		# 修改属性
    time.sleep(1)	# 停顿1秒
    print(v.num)    # 打印被修改的属性
    
for i in range(4):
    t = threading.Thread(target=task, args=(i,)) # 创建线程,执行task函数,传参i
    t.start()		# 开启线程
**执行结果**
3
3
3
3

分析:总共创建了3个线程,执行task函数,分别将v.num修改为不同的值。但由于多个线程共享同一个变量v,因而最终打印结果同为最后一个线程的修改结果。

  • 不同的线程修改threading.local类对象
import threadring
import time

v = threading.local()	#实例化local类的对象
def task(arg):
	v.num = arg
    time.sleep(1)
    print(v.num)
    
for i in range(4):
    t = threading.Thread(target=task, args=(i,))
    t.start()
执行结果:0-3的某种排列

分析:local对象为不同线程开辟一份内存空间,分别存放不同线程修改的值,内存空间互不干扰。

面向对象+线程标识符实现threading.local

分析:通过线程标识将不同的线程区分开来,即将线程标识作为关键字key,修改内容作为值value,从而为不同的线程构建互不干扰的内存空间。

简单版
import threading

# 构建Foo类对象
# 其中创建storage属性
# storage={
#			indent:{key:value}
#			1123: { num : 1}
#			1124: { num : 2}
#			.....
#		  }

class Foo(object):
    def __init__(self):
        object.__setattr__(self, "storage",{})
        
    def __setattr__(self, key, value):
        ident = threading.get_ident()
        if ident in self.storage:
            self.storage[ident][key]=value
        else:
            self.storage[ident]={key:value}
            
    def __getattr__(self, item):
        ident = threading.get_ident()
        if ident not in self.storage:
            return
        else:
            return self.storage[ident].get(item)
        
v = Foo()
def task(arg):
    v.num=arg		# __setattr__
    print(v.num)	# __getattr__
    print(v.storage)
    
for i in range(4):
    t = threading.Thread(target=task, args=(i,))
    t.start()
**执行结果**
0
{10828: {'num': 0}}
1
{10828: {'num': 0}, 12624: {'num': 1}}
2
{10828: {'num': 0}, 12624: {'num': 1}, 8260: {'num': 2}}
3
{10828: {'num': 0}, 12624: {'num': 1}, 8260: {'num': 2}, 2416: {'num': 3}}
高级版

将value包装为list列表,通过appendpop维护成栈【先进后出】

import threading

# 构建Foo类对象
# 其中创建storage属性
# storage={
#			indent:{key:[value,]}
#			1123: { num : [1,..]}
#			1124: { num : [2,..]}
#			.....
#		  }

class Foo(object):
    def __init__(self):
        object.__setattr__(self, "storage",{})
        
    def __setattr__(self, key, value):
        ident = threading.get_ident()
        if ident in self.storage:
            self.storage[ident][key].append(value)
        else:
            self.storage[ident]={key:[value,]}
            
    def __getattr__(self, item):
        ident = threading.get_ident()
        if ident not in self.storage:
            return
        else:
            return self.storage[ident][item][-1]
        
v = Foo()
def task(arg):
    v.num = arg  # __setattr__
    v.num = arg+1
    print(v.num) # __getattr__
    print(v.storage)
    
for i in range(4):
    t = threading.Thread(target=task, args=(i,))
    t.start()
**执行结果**
1
{9796: {'num': [0, 1]}}
2
{9796: {'num': [0, 1]}, 4776: {'num': [1, 2]}}
3
{9796: {'num': [0, 1]}, 4776: {'num': [1, 2]}, 5280: {'num': [2, 3]}}
4
{9796: {'num': [0, 1]}, 4776: {'num': [1, 2]}, 5280: {'num': [2, 3]}, 9488: {'num': [3, 4]}}

Flask的上下文管理

Flask上下文管理

# 项目根目录\venv\Lib\site-packages\flask\global.py
_request_ctx_stack = LocalStack()	# 请求上下文管理
_app_ctx_stack = LocalStack()		# 应用上下文管理

这里的实现借助了werkzeug中的LocalStack()类,而LocalStack()类又是借助Local()类实现的。

Local

Local源码

Local类的源码实现和上面我们所说的面向对象+线程标识符实现threading.local的简单版的原理是一样的

# 项目根目录\venv\Lib\site-packages\werkzeug\local.py
class Local(object):
    __slots__ = ("__storage__", "__ident_func__")

    def __init__(self):
        object.__setattr__(self, "__storage__", {})	
        object.__setattr__(self, "__ident_func__", get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
Local源码源码分析

我们通过下列代码的顺序逐步分析源码

local = Local()	# 实例化Local类的对象
local.num = 1   # 赋值,执行__setattr__函数
print(local.num)# 取值,执行__getattr__函数
print(local.__storage__)

local.num = 2   # 赋值,执行__setattr__函数
print(local.num)# 取值,执行__getattr__函数
print(local.__storage__)

del local.num   # 删值,执行__delattr__函数 
print(local.__storage__)
**执行结果**
1
{6340: {'num': 1}}
2
{6340: {'num': 2}}
{6340: {}}
  1. 实例化Local类对象:local=Local()

    def __init__(self):
        # self.__storage__={},用于存储所有线程的数据信息
        object.__setattr__(self, "__storage__", {})	
        # self.__ident_func_=get_ident,获取线程ID的函数
        object.__setattr__(self, "__ident_func__", get_ident)
    
  2. 赋值函数__setattr__local.num = 1

    def __setattr__(self, name, value):
        # 获取当前线程的ID
        ident = self.__ident_func__()
        # 获取类属性__storage__
        storage = self.__storage__
    
        try:
            # 如果__storage__中有该线程的数据信息,将其修改为value
            storage[ident][name] = value
        except KeyError:
            # 如果__storage__中没有有该线程的数据信息,键设为线程ID,值设为字典{name:value}
            storage[ident] = {name: value}
    

    Local类维护的__storage__属性的格式如下

     storage={
                indent:{key:value}
                1123: { num : 1}
                1124: { num : 2}
                .....
              }
    
  3. 取值函数__getattr__print(local.num)

    def __getattr__(self, name):
        try:
            # 获取当前线程的name数据,即__storage__[ident][name]
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    
  4. 删值函数__delattr__del local.num

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)
    

LocalStack

LocalStack源码

Local类的源码实现和上面我们所说的面向对象+线程标识符实现threading.local的高级版的原理是一样的

# 项目根目录\venv\Lib\site-packages\werkzeug\local.py
class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, "__ident_func__", value)

    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup():
            rv = self.top
            if rv is None:
                raise RuntimeError("object unbound")
            return rv

        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        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:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
LocalStack源码分析
ls = LocalStack()	# 实例化LocalStack类的对象
ls.push(1)   # 赋值,执行push函数
print(ls.top)# 取值,执行top函数
print(ls._local.__storage__)

ls.push(2)   # 赋值,执行push函数
print(ls.top)# 取值,执行top函数
print(ls._local.__storage__)

ls.pop()     # 删值,执行pop函数
print(ls.top)
print(ls._local.__storage__)
***执行结果***
1
{5244: {'stack': [1]}}
2
{5244: {'stack': [1, 2]}}
1
{5244: {'stack': [1]}}
  1. 实例化LocalStack类对象:ls=LocalStack()

    def __init__(self):
        # 创建_local属性,其为Local类
    	self._local = Local()
    
  2. 赋值函数pushls.push(1)

    def push(self, obj):
        # rv=self._local[__storage__][ident][stack],此时_local对象中无stack属性,故返回值None
        rv = getattr(self._local, "stack", None)
        if rv is None:
            # self._local[__storage__][ident][stack]=[]
            # rv=[]
            # self._local[__storage__][ident][stack]与rv指向同一列表
            self._local.stack = rv = []
        # self._local[__storage__][ident][stack]:[obj,]
        rv.append(obj)
        return rv
    

    LocalStack类维护的__local属性中的__storage格式如下,将线程标识作为关键字key,将{"stack": [数据1,]}作为值value

     self._local
     storage={
                indent:{key:value}
                1123: { "stack" : [1,2]}
                1124: { "stack" : [3,]}
                .....
              }
    
  3. 取值函数topprint(local.top)

       @property
        def top(self):
            try:
                # 返回栈顶元素
                return self._local.stack[-1]
            except (AttributeError, IndexError):
                return None
    
  4. 删值函数popls.pop()

    def pop(self):
        stack = getattr(self._local, "stack", None)
        # 如果当前线程的数据信息为空,无需出栈,直接返回
        if stack is None:
            return None
        # 当栈长度为1时,直接释放该线程的数据信息
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()
        
    # 其中的release_local函数
    def release_local(local):
        local.__release_local__()
    

Local与LocalStack

  • Local实现了threading.local的功能,其维护了__storage__属性,为不同线程开辟内存空间,使其互不干扰。格式如下:

     storage={
                indent:{key:value}
                1123: { num : 1}
                1124: { num : 2}
                .....
              }
    
  • LocalStack借助Local实现了栈结构,如果说 Local 提供了多线程或者多协程隔离的属性访问,那么 LocalStack 就提供了隔离的栈访问。

     storage={
                indent:{key:[value,]}
                1123: { num : [1,]}
                1124: { num : [2,]}
                .....
              }
    

LocalProxy

偏函数partial

Python 偏函数 | 菜鸟教程 (runoob.com)

看一个简单的例子有助于理解

from functools import partial
def func(a1,a2):
    print(a1,a2)
new_func = partial(func,123)
new_func(2)		# 123,2

我们先来看看flask.globals构造的偏函数

# 获取请求上下文中的name
def _lookup_req_object(name):
    # 获取当前请求的请求上下文
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    # 返回所需的信息
    return getattr(top, name)

# 返回应用上下文的name
def _lookup_app_object(name):
    # 获取当前app的应用上下文
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    # 返回所需的信息
    return getattr(top, name)
f1 = partial(_lookup_req_object, 'request')
f2 = partial(_lookup_req_object, 'session')
f3 = partial(_lookup_app_object, 'g'))

不难理解,

  • f1是获取当前请求上下文中request的函数
  • f2是获取当前请求上下文的session的函数
  • f3是获取当前应用上下文的g的函数
Local代理LocalProxy

flask.globals中定义了如下四个变量,都是LocalProxy

"""
    flask.globals
"""
# 获取当前app
def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app
current_app = LocalProxy(_find_app)#LocalProxy(app)
request = LocalProxy(partial(_lookup_req_object, 'request'))# LocalProxy(ctx.request)
session = LocalProxy(partial(_lookup_req_object, 'session'))# LocalProxy(ctx,session)
g = LocalProxy(partial(_lookup_app_object, 'g'))# LocalProxy(app_ctx.g)

目光转到LocalProxy类中

class LocalProxy(object):
    __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")

    def __init__(self, local, name=None):
        # 知识点“_类名.__属性”即获取该类的私有属性
        """
        current_app:self.__local = _find_app
        request:self.__local = partial(_lookup_req_object, 'request')
        session:self.__local = partial(_lookup_req_object, 'session')
        g:self.__local = partial(_lookup_app_object, 'g')
        """
        object.__setattr__(self, "_LocalProxy__local", local) 
        object.__setattr__(self, "__name__", name)
        if callable(local) and not hasattr(local, "__release_local__"):
            object.__setattr__(self, "__wrapped__", local)

    # 
    def _get_current_object(self):
        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            # 返回self.__local(self.__name__)
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)


    def __getattr__(self, name):
        if name == "__members__":
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]


    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
    __getitem__ = lambda x, i: x._get_current_object()[i]


我们考虑request.args参数,其执行了LocalProxy__getattr__方法

def __getattr__(self, name):
    if name == "__members__":
        return dir(self._get_current_object())
    return getattr(self._get_current_object(), name)

其中调用了_get_current_object()方法

def _get_current_object(self):
    if not hasattr(self.__local, "__release_local__"):
        return self.__local()
    try:
        return getattr(self.__local, self.__name__)
    except AttributeError:
        raise RuntimeError("no object bound to %s" % self.__name__)

再考虑session['user']='admin'session['user'],分别执行了LocalProxy__setitem__方法和__getitem__方法

def __setitem__(self, key, value):
    self._get_current_object()[key] = value

def __delitem__(self, key):
    del self._get_current_object()[key]

Flask上下文管理

继续回到Flask的上下文管理,根据Flask应用程序启动中wsgi_app的源码分析,可以知道Flask的这两个全局变量的栈中分别存放了什么值。_request_ctx_stack的栈中存放RequestContext对象,_app_ctx_stack的栈中存放AppContext对象

# 项目根目录\venv\Lib\site-packages\flask\global.py
_request_ctx_stack = LocalStack()	# 请求上下文
_app_ctx_stack = LocalStack()		# 应用上下文
  • 请求上下文
_request_ctx_stack = LocalStack()	# 请求上下文
_request_ctx_stack._local.__storage__={
    #indent:{key:[value,]}
    1123: { "stack" : [RequestContext(request,session),]}
    1124: { "stack" : [RequestContext(request,session),]}
            .....
}
  • 应用上下文
_app_ctx_stack = LocalStack()		# 应用上下文
_app_ctx_stack._local.__storage__={
    #indent:{key:[value,]}
    1123: { "stack" : [AppContext(app,g),]}
    1124: { "stack" : [AppContext(app,g),]}
            .....
}
  • 整体分析

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值