ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

04. Flsak源码分析之【上下文】

2022-06-22 01:00:06  阅读:136  来源: 互联网

标签:Flsak __ 04 request self ctx 源码 local app


4.1概述

查遍全网,发现其实也没有一个准确的定义什么是上下文。知乎上有一个通俗的回答:

每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。
– vzch

其实看这个名词的英文写法就知道大概:context

flask 中有两种context:application contextrequest context,也就是说上下文指的就是这两个封装数据的对象。也许还有一些高深的奥义,但怎么定义不用深究,怎么使用才是关键。

再来梳理下flask中上下文的管理流程:

  • 请求进来,创建ctx对象和app_ctx对象
  • ctx对象内封装了request和session,app_ctx对象内封装了app和g
  • 然后把他们入栈 -->至此,完成“上文”
  • 执行视图函数时,可以直接从栈上获取这两个对象中封装的数据
  • 完事后,再把ctx和app_ctx销毁 -->至此,就完成“下文”

 

4.2 LocalStack

在整个流程中的封装数据以及读取数据时,用到了两个东西:LocalStackLocal。它们两个的作用就是让我们可以动态地获取两个上下文的内容,在并发程序中每个视图函数都有属于自己的上下文,而不会出现混乱。

再回看一下ctx.push():

def push(self):
    app_ctx = _app_ctx_stack.top
    if app_ctx is None or app_ctx.app != self.app:
        # 创建AppContext对象,里面封装了app对象和g
        app_ctx = self.app.app_context()  # AppContext()
        # app_ctx入栈
        app_ctx.push()
        self._implicit_app_ctx_stack.append(app_ctx)
    else:
        self._implicit_app_ctx_stack.append(None)

	# 这里_request_ctx_stack是LocalStack()对象,在globals.py中定义的全局对象
    _request_ctx_stack.push(self) # 注意传递了self参数

上面两个对象的push()入栈,追溯源码其实最终都是走到了LocalStack()对象的push()方法:

class LocalStack:
    # 创建local对象用于存储数据
    def __init__(self) -> None:
        self._local = Local()

    def __release_local__(self) -> None:
        self._local.__release_local__()

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

        return LocalProxy(_lookup)
	
    # 先将ctx/app_ctx存入一个列表中,再把列表存入local对象中
    def push(self, obj: t.Any) -> t.List[t.Any]:
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", []).copy()
        rv.append(obj)
        self._local.stack = rv
        return rv

    # 从local对象中的列表内取出末尾元素
    def pop(self) -> t.Any:
        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()
        
	# 从local对象中的列表内取出末尾元素
    @property
    def top(self) -> t.Any:
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

可以看到,这个LocalStack类内有pushpoptop 方法,操作的是存储在Local中的ctx/app_ctx数据。

所以,这个类其实是基于 Local 实现的结构。

 

4.3 Local

class Local:
    __slots__ = ("_storage",)
	
    # 设置self._storage为一个ContextVar对象,用于保存数据
    def __init__(self) -> None:
        object.__setattr__(self, "_storage", ContextVar("local_storage"))

    def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]:
        return iter(self._storage.get({}).items())
	
    # 当调用Local对象时,返回对应的LocalProxy
    def __call__(self, proxy: str) -> "LocalProxy":
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    # Local类中特有的方法,用于清空数据
    def __release_local__(self) -> None:
        self._storage.set({})

    def __getattr__(self, name: str) -> t.Any:
        values = self._storage.get({})
        try:
            return values[name]
        except KeyError:
            raise AttributeError(name) from None

    def __setattr__(self, name: str, value: t.Any) -> None:
        values = self._storage.get({}).copy()  # 从ContextVar对象中取数据
        values[name] = value  # 编辑数据,如: {"stark":[ctx,]}
        self._storage.set(values) # 存入新的数据

    def __delattr__(self, name: str) -> None:
        values = self._storage.get({}).copy()
        try:
            del values[name]
            self._storage.set(values)
        except KeyError:
            raise AttributeError(name) from None

可以看到,Local 对象内部的数据都是保存在 __storage__ 属性的,它是一个ContextVar对象

NOTE:werkzeug自2.0.0版本后引入的ContextVar替代了旧版的threading.local,他是python3.7开始支持的,用于声明一个新的上下文变量(参阅:werkzeug官方文档contextvars上下文变量

看一个示例了解用法:

from contextvars import *

storage = ContextVar('local_storage')

x = {"stark": ["aaa", ]}

storage.set(x)  # 存入数据
print(storage)  # <ContextVar name='local_storage' at 0x000001B23810C7C0>

y1 = storage.get({})  # 取数据
y2 = storage.get() # 同上
print(y1)  # {'stark': ['aaa']}
print(y2)  # {'stark': ['aaa']}

storage.set({})  # 清空数据
z = storage.get()
print(z)  # {}

 

4.4 LocalProxy

回看LocalStark中,有个__call__方法,返回的是LocalProxy对象

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

我们说LocalProxyLocal对象的代理对象,源码:

class LocalProxy:
    __slots__ = ("__local", "__name", "__wrapped__")

    def __init__(
        self,
        local: t.Union["Local", t.Callable[[], t.Any]],
        name: t.Optional[str] = None,
    ) -> None:
        object.__setattr__(self, "_LocalProxy__local", local) # 设置self.__local
        object.__setattr__(self, "_LocalProxy__name", name) # 设置self.__name

        if callable(local) and not hasattr(local, "__release_local__"):
            object.__setattr__(self, "__wrapped__", local) 

    def _get_current_object(self) -> t.Any:
        if not hasattr(self.__local, "__release_local__"):  # type: ignore
            return self.__local()  # type: ignore

        try:
            return getattr(self.__local, self.__name)  # type: ignore
        except AttributeError:
            name = self.__name  # type: ignore
            raise RuntimeError(f"no object bound to {name}") from None

    __doc__ = _ProxyLookup(  # type: ignore
        class_value=__doc__, fallback=lambda self: type(self).__doc__
    )

    __repr__ = _ProxyLookup(  # type: ignore
        repr, fallback=lambda self: f"<{type(self).__name__} unbound>"
    )
    __str__ = _ProxyLookup(str)  # type: ignore
    __bytes__ = _ProxyLookup(bytes)


这里实现的关键是把通过参数传递进来的 Local 实例保存在 __local 属性中,并定义了 _get_current_object() 方法获取当前的对象。

NOTE:前面双下划线的私有属性,会保存到 _ClassName__variable 中。所以这里通过 “_LocalProxy__local” 设置的值,后面可以通过 self.__local 来获取。

然后 LocalProxy 重写了所有的魔术方法(名字前后有两个下划线的方法,上面只列出部分),具体操作都是转发给代理对象的。

此时,我们再看globals.py中定义的上下文的使用:

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app


# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app: "Flask" = LocalProxy(_find_app)  
request: "Request" = LocalProxy(partial(_lookup_req_object, "request"))  
session: "SessionMixin" = LocalProxy(partial(_lookup_req_object, "session"))
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g"))  

这里可以看到两个context中封装的对象的提取都是经由代理对象LocalProxy来转发给Local执行的。

比如:request=LocalProxy(partial(_lookup_req_object, "request")),这里的partial是偏函数,可以在调用之前为函数提前传参,起到固定参数的作用(不会执行),举个例子:

from functools import partial
def add(a,b,c):
    print(a+b+c)
new_func  = partial(add,1,2)  # 为add函数提前传两个值,成为一个新函数
new_func(3) # 再调用只需要传剩下的值

理解了偏函数,那这里就相当于:

request= LocalProxy(partial(_lookup_req_object, "request"))
# new_func = _lookup_req_object("request") # 不会执行
# 相当于request= LocalProxy(new_func),调用init方法:
class LocalProxy:
    def __init__(self,local,name=None):  # local = new_func
	self.__local = new_func
	self.name:None

因此LocalProxy中的_get_current_object方法返回的self.__local(),实际上就是执行new_func(),最终从ctx里找到并赋值给request对象。

# new_func() = :
_lookup_req_object("request")
	top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, "request")

同理,当我们在视图函数里执行print(request)时,就会调用LocalProxy里的__str__,同理当你request.method的时候,就会调用对应的__getattr__方法。这就是一个典型的代理模式使用。

至此,上下文中的存取数据的流程就能大概理清楚了。

 

最后,学以致用,一个简单的例子再来理解下上下文的使用:

from flask import Flask, session, request, current_app, g

app = Flask(__name__,static_url_path='/xx')

@app.route('/index')
def index():
    # 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
    """
    session['k1'] = 123
    print(request.args)
    print(request.form)

    return 'hello world'

if __name__ == '__main__':
    app.run()

标签:Flsak,__,04,request,self,ctx,源码,local,app
来源: https://www.cnblogs.com/chidafy/p/16398996.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有