Loading

hellohelp

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]}}
    • 获取时返回各自线程的最后一次值(此处即设置的值)。

输出结果:

  • 每个线程打印自己设置的 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()

总结:

  1. Flask 中的 local 类:
    • 功能:类似于 threading.local,为每个线程开辟独立空间存储数据。
    • 实现机制:内部维护一个字典,以线程(或协程)ID 为 key,进行数据隔离。
    • 示例代码:
      __storage__ = {
          1211: {'k1': 123}
      }
      obj = Local()
      obj.k1 = 123
      
  2. 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('大魔方')
  1. 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)信息。
  1. 上下文管理
  • 请求上下文管理:
    • 通过_request_ctx_stack.push('小魔方')进行管理。
  • 应用上下文管理:
    • 通过_app_ctx_stack.push('大魔方')进行管理。
      总结
  • _request_ctx_stack用于管理每次HTTP请求的上下文,确保每个请求的独立性和安全性。
  • _app_ctx_stack用于管理应用级别的上下文,提供应用范围内的数据共享。
    以上内容精简了Flask中LocalStack对象的使用场景和具体管理方式。

✅上下文管理示意图

![屏幕截图 2025-06-30 201725](C:\Users\lx\Pictures\Screenshots\屏幕截图 2025-06-30 201725.png)

十一、源码初识

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 对象,其内部封装了 Appg
  • 然后 ctx.push 触发将 ctxapp_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
    • 销毁上下文对象

posted @ 2025-07-15 13:05  HordorZzz  阅读(18)  评论(0)    收藏  举报