对 flask 框架中的全局变量 request 探究

news/2025/2/27 9:42:28

在 Python 的 Web 开发框架 flask 中有这样的四个全局变量,我们经常会使用它们来存取数据,在处理请求过程中它们是非常方便的,因为它们的实现方法是相同的,所以这里我们重点探究一下 request 的使用,其它的都是类似的。下面是 flask.globals 的代码(我删减了一部分):

python">"""
删减了一部分代码,这里的关注重点是 request 变量。
"""

# -*- coding: utf-8 -*-
"""
    flask.globals
"""

from functools import partial
from werkzeug.local import LocalStack, LocalProxy


_request_ctx_err_msg = '''\
Working outside of request context.

This typically means that you attempted to use functionality that needed
an active HTTP request.  Consult the documentation on testing for
information about how to avoid this problem.\
'''


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


# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

从这里我们可以看到 current_app, request, session, g 的定义是类似的,所以我们这里只探究 request 即可。这几个变量都是全局变量,但是我们在使用时并不需要关心这点,因为框架已经帮我们做好了封装。每个线程或者协程只能访问自己特定的资源,不同线程或线程之间不会互相冲突。

1.1 Local 类

我们直接跳转到 LocalProxy 所在的文件,这个文件里面定义了 Local, LocalStack, LocalProxy 类。

python">"""
Local 类是 flask 中实现类似于 threadlocal 存储的类,只不过 threadlocal 是绑定了线程,
而它可以绑定线程、协程等,如果理解了前者,对于后者也会很容易掌握的。

简单来说,它是一个全局字典,每个线程或者协程会在其中存储它们各自的数据,数据的键是它们
的标识符(唯一性),这样存取数据就不会冲突了,从用户的角度来看一切都是透明的,他们只是从
它里面存取数据,而不用考虑多线程数据的冲突。

它只有两个属性:

- __storage__
- __ident_func__

`__storage__` 是用来存储数据的,`__ident_func__` 是线程或者协程的唯一标识符,用于区分是哪一个具体的线程或者协程。
关于它的具体实现,会使用一些高级的 Python 语法,不过对于我们用户来说知道它的作用即可,具体的实现就超出
理解的范围了,学习一定要有取舍。

这里还要关注一下 `__call__` 方法,它会传入当前实例和一个 proxy 参数,创建一个 LocalProxy 实例,这里这个 proxy 是代理的实例的名字。
"""

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)

1.2 LocalStack 类

python">"""
这个类和 Local 类很相似,不过它是数据是存储在一个栈中的,也就是说值的部分是一个列表,
按照栈的方式进行存取数据。Flask 中也是直接用的 LocalStack 类,而不是 Local 类。
"""

class LocalStack(object):
    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead.  This is best explained with an example::

        >>> ls = LocalStack()
        >>> ls.push(42)
        >>> ls.top
        42
        >>> ls.push(23)
        >>> ls.top
        23
        >>> ls.pop()
        23
        >>> ls.top
        42

    They can be force released by using a :class:`LocalManager` or with
    the :func:`release_local` function but the correct way is to pop the
    item from the stack after using.  When the stack is empty it will
    no longer be bound to the current context (and as such released).

    By calling the stack without arguments it returns a proxy that resolves to
    the topmost item on the stack.

    .. versionadded:: 0.6.1
    """

    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):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        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):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None

1.3 LocalProxy 类

python">@implements_bool
class LocalProxy(object):
    """Acts as a proxy for a werkzeug local.  Forwards all operations to
    a proxied object.  The only operations not supported for forwarding
    are right handed operands and any kind of assignment.

    Example usage::

        from werkzeug.local import Local
        l = Local()

        # these are proxies
        request = l('request')
        user = l('user')


        from werkzeug.local import LocalStack
        _response_local = LocalStack()

        # this is a proxy
        response = _response_local()

    Whenever something is bound to l.user / l.request the proxy objects
    will forward all operations.  If no object is bound a :exc:`RuntimeError`
    will be raised.

    To create proxies to :class:`Local` or :class:`LocalStack` objects,
    call the object as shown above.  If you want to have a proxy to an
    object looked up by a function, you can (as of Werkzeug 0.6.1) pass
    a function to the :class:`LocalProxy` constructor::

        session = LocalProxy(lambda: get_current_request().session)
    """

    __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "__name__", name)
        if callable(local) and not hasattr(local, "__release_local__"):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, "__wrapped__", local)

1.4 总结:Local, LocalStack, LocalProxy 的简单使用

为了便于理解它们几个的作用,这里提供一个示例用于演示:

python">from werkzeug import LocalStack, LocalProxy

class Request:

    def __init__(self, method: str, url: str, query: str):
        self.method = method
        self.url = url
        self.query = query
    
    def __repr__(self):
        return f'{">"*30}\nmethod: {self.method}\nurl: {self.url}\nquery: {self.query}\n{"<"*30}\n'


class RequestContext:

    def __init__(self, request):    # request 是 RequestContext 的属性
        self.request = request


def _lookup_req_object(name):
    top = request_stack.top
    if top is None:
        raise RuntimeError("Working outside of request context.")
    return getattr(top, name)


request_stack = LocalStack()
request = LocalProxy(lambda: _lookup_req_object('request')) # 源码里面是偏函数,这里我用lambda 代替


if __name__ == "__main__":
    # 原始对象测试
    print("原始对象测试:")
    req = Request("GET", "/v1/user", {"name": "peter"})
    req_ctx = RequestContext(req)
    print("method: ", req.method)
    print("url: ", req.url)
    print("query: ", req.query)
    print("original request", req)
    print('-'*30)
    print("代理对象测试:")        # 将 RequestContext 对象推送 LocalStack 实例中,
    request_stack.push(req_ctx)   # 然后我们直接访问 request 对象(代理对象)
    print("method: ", request.method)
    print("url: ", request.url)
    print("query: ", request.query)
    print("request: ", request)
    print("proxied object: \n", request._get_current_object())


    target_func = lambda r: print(r.method)
    # 在当前线程中访问全局 request 对象
    print("*"*30)
    target_func(request)
    print("*"*30)
    # 尝试在另一个线程中访问全局 request 对象,guess what will happen?
    from threading import Thread
    t = Thread(target=target_func, args=(request,))
    t.start()
    t.join()

Copy and Paste the demo code, Run it, try it out!

当尝试在当前线程之外访问 request 中的内容,将会导致一个运行时异常,因为 request 是绑定到特定线程或者协程的,如果使用一个新的线程或者协程,它在全局 request_stack 中是没有数据的,因此会异常。所以一定要注意,不要在变量的上下文范围之外去读写数据,除非手动推入数据。

在这里插入图片描述


http://www.niftyadmin.cn/n/5869897.html

相关文章

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(四)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;四&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响应&…

【Uniapp-Vue3】点击将内容复制到剪切板

具体使用方法在官网&#xff1a; uni-app官网https://uniapp.dcloud.net.cn/api/system/clipboard.html大致使用方法如下&#xff1a; // value是需要复制的值 function copyValue (value) { uni.setClipboardData({data: value,success: res>{// 复制成功逻辑},fail:err&…

Java基础关键_011_ String 类与正则表达式(二)

目 录 一、正则表达式 1.说明 2.应用 3. 相关方法 &#xff08;1&#xff09;replace(CharSequence target, CharSequence replacement) &#xff08;2&#xff09;replaceAll(String regex, CharSequence replacement) &#xff08;3&#xff09;split(String regex) &…

Go 1.24版本在性能方面有哪些提升?

Go 1.24版本在性能方面有多项显著提升&#xff0c;主要包括以下几点&#xff1a; 基于Swiss Tables的新内置map实现&#xff1a;这种实现方式通过压缩索引和更高效的查找机制&#xff0c;降低了内存使用量并提高了查询速度。根据测试&#xff0c;某些场景下性能提升接近50%[1][…

【Akashic Records】《命若琴弦》

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: Akashic Records 文章目录 &#x1f4af;观后感命运的无情与生命的坚持希望的火种与人生的意义虚无与活在当下生死的辩证与享受当下结语 &#x1f4af;观后感 命若琴弦 生命的意义本不在向外的寻取&#xff0c;而在…

腾讯云服务器地域怎么选?2025地域和可用区城市表,选择攻略

腾讯云服务器地域如何选择&#xff1f;可用区是什么&#xff1f;腾讯云服务器节点分布在哪个城市&#xff1f;腾讯云百科txybk.com整理2025年最新腾讯云服务器地域选择方法、地域和可用区分布表、可用区选择方法、云服务器节点分布城市对照表&#xff0c;在腾讯云百科 txy.wiki…

深入解析React性能优化三剑客:React.memo、useMemo与useCallback

目录 渲染机制基础 React的渲染流程解析组件重渲染的根本原因性能优化的核心目标 React.memo深度解析 组件级缓存原理浅比较机制详解自定义比较函数实现 useMemo核心技术 值缓存机制剖析引用稳定性控制复杂计算场景实战 useCallback终极指南 函数缓存本质闭包陷阱解决方案事…

基于SpringBoot的“流浪动物救助系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“流浪动物救助系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统首页界面 系统…