当前位置: 首页 > article >正文

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

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

"""
删减了一部分代码,这里的关注重点是 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 类。

"""
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 类

"""
这个类和 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 类

@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 的简单使用

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

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.kler.cn/a/562734.html

相关文章:

  • 1688代采系统:自动化采购的高效解决方案
  • 解决免费 PDF 发票打印痛点的实用工具
  • Pandas 统计分析基础 之 读写不同数据源的数据①
  • 智能优化算法:雪橇犬优化算法(Sled Dog Optimizer,SDO)求解23个经典函数测试集,MATLAB
  • Word(2010)排版技巧
  • 网络安全之日志审计 网络安全审计制度
  • python 把数组转字符串的4种方法
  • Vue 3 搭建前端模板并集成 Ant Design Vue(2025)
  • 给字符串加密解密
  • Redis缓存淘汰算法——LRU
  • Redis SCAN 命令详解:安全遍历海量键的利器
  • VidSketch:具有扩散控制的手绘草图驱动视频生成
  • Eclipse安装和配置环境教程包含下载、安装、汉化(附安装包)
  • 深入浅出Spring Boot框架:从入门到精通
  • 力扣热题 100:滑动窗口专题两道题详细解析(JAVA)
  • macpro m1 安装deepseek
  • Python【数据处理】高级编程
  • 流程管理和质量体系管理怎样有效的整合
  • SSD 固态硬盘存储密度的分区
  • 什么是 Java 中的线程安全?