前情知识点
python的魔术方法
这里只讨论下述内容会用到的魔术方法
__init__(self)
初始化方法:
触发机制:实例化对象之后立即触发
参数:至少有一个self,接收当前对象,其他参数根据需要进行定义
返回值:无
作用:初始化对象的成员
__call__
调用对象的魔术方法
触发时机:将对象当作函数调用时触发,方式: 对象()
参数:至少一个self接收对象,其余根据调用时参数决定
返回值:根据情况而定
作用:可以将复杂的步骤进行合并操作,减少调用的步骤,方便使用
注意:无
__setattr__(self,key,value)
触发时机:设置对象成员值的时候触发
参数:1个当前对象的self,一个是要设置的成员名称字符串,一个是要设置的值
返回值:无 过程操作
作用:接管设置操作,可以在设置前之前进行判断验证等行为
注意:在当前方法中无法使用成员=值的方式直接设置成员,否则会无限递归,必须借助object的设置方法来完成
object.__setattr__(参数1,参数2,参数3)
__getattr__(item)
触发时机:获取不存在的对象成员时触发
参数:一个是接收当前对象的self,一个是获取成员名称的字符串
返回值:必须有值
作用:为访问不存在的属性设置值
注意:getattribute无论何时都会在getattr之前触发,触发了getattribute就不会在触发getattr了
__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列表,通过append
和pop
维护成栈【先进后出】
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: {}}
-
实例化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)
-
赋值函数
__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} ..... }
-
取值函数
__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)
-
删值函数
__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]}}
-
实例化LocalStack类对象:
ls=LocalStack()
def __init__(self): # 创建_local属性,其为Local类 self._local = Local()
-
赋值函数
push
:ls.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,]} ..... }
-
取值函数
top
:print(local.top)
@property def top(self): try: # 返回栈顶元素 return self._local.stack[-1] except (AttributeError, IndexError): return None
-
删值函数
pop
:ls.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),]}
.....
}
- 整体分析