flask之路【第四篇】上下文及源码实现
⚙️本教程工程文件{ProjectName}:Practice_flask_0618
{ProjectName}:Practice_flask_0619
十、flask上下文实现原理
10.1 threading.local()为每个线程创建内存
这个是python中的,flask中有其他的更好
- 没用threading.local之前
import time
import threading
class Foo(object):
def __init__(self):
self.num = 0
val2 = Foo()
def task(i):
val2.num = i
time.sleep(1)
print(val2.num)
for i in range(4):
t = threading.Thread(target=task, args=(i,))
t.start()
结果是
>>>3 >>>3 >>>3 >>>3
- 用threading.local()为每个线程创建内存
import time
import threading
# 当每个线程在执行 val1.xx=1 ,在内部会为此线程开辟一个空间,来存储 xx=1
# val1.xx 找到此线程自己的内存地址去取自己存储 xx
val1 = threading.local()
def task(i):
val1.num = i
time.sleep(1)
print(val1.num)
for i in range(4):
t = threading.Thread(target=task, args=(i,))
t.start()
结果是
>>>0 >>>1 >>>2 >>>3
实现方式是通过匹配线程ID实现的,为每个ID创建内存
10.2 栈
描述:
后进先出,通过列表可以实现一个栈。
代码示例:
v =
v.append(44)
v.pop()
应用场景:
- 节流
解读: - 栈的定义:栈是一种后进先出(LIFO, Last In First Out)的数据结构。
- 实现方式:可以通过列表来实现栈,利用
append()
方法添加元素(相当于入栈),pop()
方法移除最后一个元素(相当于出栈)。 - 示例代码:
v =
:初始化一个列表。v.append(44)
:向列表末尾添加元素44
,此时v
变为 ``。v.pop()
:移除并返回列表的最后一个元素44
,此时v
变为 ``。
- 应用场景:节流,即控制函数的执行频率,常用于事件处理中防止高频触发。
10.3 面向对象-反射
class Foo(object):
def __setattr__(self, key, value):
print(key, value)
def __getattr__(self, item):
print(item)
obj = Foo()
obj.x = 123
obj.x
# DRF 中的 request 相关内容(DRF是Django中的内容)
request.data
request.query_params
request._request
request._request.POST
request._request.GET
#
request.POST 也可以用他会调用request._request.POST,其他.Get同理
request.GET
💡小知识点:重写___setattr__和_getattr_
# 重写__setattr__和__getattr__方法。在对象里放一个字典
class Local(object):
def __init__(self):
object.__setattr__(self, "storage", {})
def __setattr__(self, key, value):
self.storage[key] = value
def __getattr__(self, item):
return self.storage.get(item)
local = Local()
local.x1 = 123
print(local.x1)
10.4 线程的唯一标识的获取
import threading
from threading import get_ident
def task():
ident = get_ident() # 获取当前线程标识
print(ident)
for i in range(20):
t = threading.Thread(target=task)
t.start()
10.5 自定义threading.local(见2.11,这里是自己写的threading.local)
import threading
'''
storage = {
1111: {'x1': 0},
1112: {'x1': 1},
1113: {'x1': 2},
1114: {'x1': 3},
1115: {'x1': 4}
}
'''
class Local(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
return self.storage[ident].get(item)
local = Local()
def task(arg):
local.x1 = arg
print(local.x1)
for i in range(5):
t = threading.Thread(target=task, args=(i,))
t.start()
10.6💎加强版threading.local
import threading
'''
storage = {
1111: {'x1': []},
1112: {'x1': []},
1113: {'x1': []},
1114: {'x1': []},
1115: {'x1': []},
1116: {'x1': []}
}
'''
class Local(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
return self.storage[ident][item][-1]
local = Local()
def task(arg):
local.x1 = arg
print(local.x1)
for i in range(5):
t = threading.Thread(target=task, args=(i,))
t.start()
上述代码实现了一个简单的线程局部存储(Thread-Local Storage)机制,具体解读如下:
storage
是一个字典,键为线程标识(这里预设了 1111 到 1116),值为另一个字典,该字典的键为'x1'
,值为一个空列表。这用于存储每个线程的局部变量。- 线程隔离:每个线程通过自身ID (
ident
) 在storage
中维护独立的数据空间。 - 数据存储:
- 设置属性时(如
local.x1 = 5
),值被追加到列表中(而非覆盖)。 - 获取属性时(如
local.x1
),返回该线程对应属性最后一次设置的值(列表最后一个元素)。
- 设置属性时(如
- 初始化:使用
object.__setattr__
绕过自定义的__setattr__
,避免递归问题。 - 启动5个线程:每个线程执行
task(i)
,i
值为 0 到 4。 - 线程行为:
- 线程A设置
local.x1 = 0
→ 存储为{A: {'x1': [0]}}
- 线程B设置
local.x1 = 1
→ 存储为{B: {'x1': [1]}}
- 获取时返回各自线程的最后一次值(此处即设置的值)。
- 线程A设置
输出结果:
-
每个线程打印自己设置的
arg
值(0,1,2,3,4)。 -
输出顺序不确定(线程调度随机),但每个线程必然输出自己的值,例如:
-
0
2
1
4
3
总结:
此代码演示了一个简单的线程局部存储实现,核心是通过线程ID隔离数据,确保每个线程操作独立的数据副本。尽管存在优化空间,但清晰展示了多线程环境下数据隔离的原理。
10.7.flask中的源码关于local的实现
from threading import get_ident
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):
"""Create a proxy for a name."""
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)
class LocalStack(object):
def __init__(self):
self._local = Local()
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:
return stack[-1]
else:
return stack.pop()
@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
obj = LocalStack()
obj.push('汪洋')
obj.push('成说')
print(obj.top)
obj.pop()
obj.pop()
总结:
- Flask 中的
local
类:- 功能:类似于
threading.local
,为每个线程开辟独立空间存储数据。 - 实现机制:内部维护一个字典,以线程(或协程)ID 为 key,进行数据隔离。
- 示例代码:
__storage__ = { 1211: {'k1': 123} } obj = Local() obj.k1 = 123
- 功能:类似于
- Flask 中的
LocalStack
类:- 依赖
local
对象,local
对象负责存储数据,LocalStack
对象将local
中的值维护成一个栈。 - 示例代码:
简要解释:__storage__ = { 1211: {'stack': ['k1', []]} } obj = LocalStack() obj.push('k1') obj.top obj.pop()
- 依赖
local
类 用于实现线程局部存储,确保每个线程的数据独立性。LocalStack
类 在local
的基础上,提供栈的功能,方便进行数据的压栈和弹栈操作。
10.8 python中单例模式的实现的几种方式
该模式的主要目的是确保某一个类只有一个实例存在,避免资源浪费
-
1.使用模块
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成
.pyc
文件,当第二次导入时,就会直接加载.pyc
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:mysingleton.py
class Singleton(object): def foo(self): pass singleton = Singleton()
将上面的代码保存在文件
mysingleton.py
中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对
from a import singleton
- 2.使用
__new__
为了使类只能出现一个实例,我们可以使用 __new__
来控制实例的创建过程,代码如下:
class MyClass(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(MyClass, cls).__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self):
pass
class HerClass(MyClass):
a = 1
在上面的代码中,我们将类的实例和一个类变量 _instance
关联起来,如果 cls._instance
为 None 则创建实例,否则直接返回 cls._instance
。
执行情况如下:
one = HerClass()
two = HerClass()
print(one == two) #True
print(one is two) #True
print(id(one), id(two)) #42818864 42818864
-
3.使用装饰器
装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下:
from functools import wraps def singleton(cls): instances = {} @wraps(cls) def getinstance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return getinstance @singleton class MyClass(object): a = 1
在上面,我们定义了一个装饰器
singleton
,它返回了一个内部函数getinstance
,该函数会判断某个类是否在字典instances
中,如果不存在,则会将cls
作为 key,cls(*args, **kw)
作为 value 存到instances
中,否则,直接返回instances[cls]
。 -
4.基于metaclass方式实现
相关知识
""" 1.类由type创建,创建类时,type的__init__方法自动执行,类() 执行type的 __call__方法(类的__new__方法,类的__init__方法) 2.对象由类创建,创建对象时,类的__init__方法自动执行,对象()执行类的 __call__ 方法 """
例子:
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): pass obj = Foo() # 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。 obj() # 执行Foo的 __call__ 方法
元类(metaclass)可以控制类的创建过程,它主要做三件事:
- 拦截类的创建
- 修改类的定义
- 返回修改后的类
使用元类实现单例模式的代码如下:
class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] # Python2 # class MyClass(object): # __metaclass__ = Singleton # Python3 class MyClass(metaclass=Singleton):
-
5.使用类(Singleton类),但是这样当使用多线程时会存在问题,不推荐使用
10.9.Flask源码中的LocalStack对象(上下文管理)
# context locals
__storage__ = {
1111: {'stack': [RequestContext(request, session), ]},
1123: {'stack': [RequestContext(request, session), ]},
}
_request_ctx_stack = LocalStack()
__storage__ = {
1111: {'stack': [AppContext(app, g), ]},
1123: {'stack': [AppContext(app, g), ]},
}
_app_ctx_stack = LocalStack()
_request_ctx_stack.push('小魔方')
_app_ctx_stack.push('大魔方')
- Flask源码中总共有2个
LocalStack
对象,分别是_request_ctx_stack
和_app_ctx_stack
。
- _request_ctx_stack:
- 存储结构:
__storage__ = {1111: {'stack': }, 1123: {'stack': }}
- 作用:管理请求上下文(
RequestContext
),包含请求(request
)和会话(session
)信息。
- 存储结构:
- _app_ctx_stack:
- 存储结构:
__storage__ = {1111: {'stack': }, 1123: {'stack': }}
- 作用:管理应用上下文(
AppContext
),包含应用(app
)和全局变量(g
)信息。
- 存储结构:
- 上下文管理
- 请求上下文管理:
- 通过
_request_ctx_stack.push('小魔方')
进行管理。
- 通过
- 应用上下文管理:
- 通过
_app_ctx_stack.push('大魔方')
进行管理。
总结
- 通过
- _request_ctx_stack用于管理每次HTTP请求的上下文,确保每个请求的独立性和安全性。
- _app_ctx_stack用于管理应用级别的上下文,提供应用范围内的数据共享。
以上内容精简了Flask中LocalStack
对象的使用场景和具体管理方式。
✅上下文管理示意图

十一、源码初识
11.1 项目启动
- 实例化Flask对象
app = Flask(__name__)
1.对app对象封装一些初始化的值。
app.static_url_path
app.static_folder
app.template_folder
app.view_functions = {}
2.添加静态文件的路由
self.add_url_rule(
self.static_url_path + "/",
endpoint="static",
host=static_host,
view_func=self.send_static_file,
)
3.实例化了 url_map 的对象,以后在 map 对象中放【/index/ 函数的对象应观】
class Flask(object):
url_rule_class = Rule
url_map_class = Map
def __init__(self, ...):
self.static_url_path
self.static_folder
self.template_folder
self.view_functions = {}
self.url_map = self.url_map_class()
app = Flask()
app.view_functions
app.url_rule_class
- 加载配置文件(给app的config进行赋值)
from flask import Flask
app = Flask(__name__, static_url_path='/xx')
app.config.from_object('xx.xx')
配置文件处理
1. 读取配置文件中的所有键值对,并将键值对全都放到Config对象。(Config是一个字典)
2. 把包含所有配置文件的Config对象,赋值给_app.config
- 添加路由映射
from flask import Flask
app = Flask(__name__, static_url_path='/xx')
@app.route('/index')
def index():
return 'hello world'
步骤说明
(实际调用add_url_rule(rule, endpoint, f, **options)函数。)
1. 将 url = /index 和 methods = [GET, POST] 和 endpoint = "index" 封装到 Rule 对象
2. 将 Rule 对象添加到 app.url_map 中。
3. 把 endpoint 和函数的对应关系放到 app.view_functions 中。
- 截止目前
app.config
app.url_map
app.view_functions
- 运行flask
from flask import Flask
app = Flask(__name__, static_url_path='/xx')
@app.route('/index')
def index():
return 'hello world'
if __name__ == '__main__':
app.run()
1. 内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求到来。
2. 一旦有用户请求,执行`app.__call__`方法。
class Flask(object):
def __call__(self, environ, start_response):
pass
def run(self):
run_simple(host, port, self, **options)
if __name__ == '__main__':
app.run()
11.2用户请求到来
请求进来 1.先封装成request对象 2.根据url进行路由匹配,得到endpoint 3.创建一个空session 4.把request和session和app进行打包,封装进request_context对象。 5.将app和g封装到app_context对象中。
request_context和app_context对象,会在返回Response后进行销毁。environ原始请求对象
有用户请求到来
- 创建
ctx = RequestContext
对象,其内部封装了Request
对象和session
数据。 - 创建
app_ctx = ApplicationContext
对象,其内部封装了App
和g
。 - 然后
ctx.push
触发将ctx
和app_ctx
分别通过自己的LocalStack
对象将其放入到Local
中,Local
的本质是以线程 ID 为 key,以{"stack":[]}
为 value 的字典。
{
1111: {"stack": [ctx,]}
}
{
1111: {"stack": [app_ctx,]}
}
注意:以后再想要获取 request
/ session
/ app
/ g
时,都需要去 local
中获取。
- 执行所有的
before_request
函数 - 执行视图函数
- 执行所有
after_request
函数(session
加密放到cookie
中) - 销毁
ctx
和 `app_ctx
11.3了解源码流程之后,使用:session、request、app、g私有成员
- LocalProxy (flask 2.x)
LocalProxy是Flask中Werkzeug工具提供的一个代理类,用于包装Local实例的调用,实现在全局范围内动态访问线程或协程特定的数据,并保证线程安全
import functools
class LocalProxy(object):
def __init__(self, local):
object.__setattr__(self, "_LocalProxy__local", local) #self._LocalProxy__local=local
def __setitem__(self, key, value):
self._get_current_object()[key] = value
def __getattr__(self, name):
return getattr(self._get_current_object(), name)
def _get_current_object(self):
return self.__local() #self._LocalProxy__local
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
session = LocalProxy(functools.partial(_lookup_req_object, "session")) # 函数() 自动传入session
request = LocalProxy(functools.partial(_lookup_req_object, "request")) #函数() 自动传入request
LocalProxy (flask 3.x)中发生了变化,应用原理参看flask源码解析的文章。
#flask3.1.1中 globals.py源码
_cv_app= ContextVar("flask.app_ctx")
app_ctx= LocalProxy(_cv_app, unbound_message=_no_app_msg)
current_app = LocalProxy(_cv_app, "app", unbound_message=_no_app_msg)
g = LocalProxy(_cv_app, "g", unbound_message=_no_app_msg)
request_ctx = LocalProxy(_cv_request, unbound_message=_no_req_msg)
request = LocalProxy(_cv_request, "request", unbound_message=_no_req_msg)
session = LocalProxy(_cv_request, "session", unbound_message=_no_req_msg)
- 偏函数
import functools
'''
偏函数
'''
def func(a1, a2):
print(a1, a2)
new_func = functools.partial(func, 123)
new_func(2)
- 私有成员
class Foo:
def __init__(self):
self.name = 'alex'
self.__age = 123
obj = Foo()
print(obj.name)
print(obj._Foo__age) #强制获取私有成员
-
setattr
-
setitem
request_ctx,app_ctx,session, request, current_app, g 全部都是LocalProxy对象。 """" session['x'] = 123 ctx.session['x'] = 123 request.method ctx.request.method current_app.config app_ctx.app.config g.x1 app_ctx.g.x1 """"
11.4 g到底是个什么鬼?
在一次请求请求的周期,可以在g中设置值,在本次的请求周期中都可以读取或复制。相当于是一次请求周期的全局变量。
from flask import Flask, g
app = Flask(__name__, static_url_path='/xx')
@app.before_request
def f1():
g.x1 = 123
@app.route('/index')
def index():
print(g.x1)
return 'hello world'
if __name__ == '__main__':
app.run()
💎总结:
第一阶段:启动flask程序,加载特殊装饰器、路由,把他们封装 app= Flask对象中。
- 第二阶段:请求到来
- 创建上下文对象:应用上下文、请求上下文。
- 执行before / 视图 / after
- 销毁上下文对象