Flask解析(一):Local、LocalStak、LocalProxy

Local是什么?

无论你接触到的是threading.Local还是werkzeug.Local,它们都代表一种变量——每个线程自己的全局变量。



全局变量,一般位于进程的堆上。一个进程的所有线程都可以访问同一个全局变量,因为它们共享着进程的地址空间,所以每一个线程都可以访问,这也带来了问题,如果多个线程同时访问同一个变量,会对该变量的读写造成不可预估的结果,所以通常我们会使用锁或其他的同步机制来控制线程之间对共享变量的访问。当然了,这不是本文关注的地方。

说回Local,我们在开头提到Local是线程自己的全局变量。所谓线程自己的,就是说该“全局变量”只有拥有的线程自己可以访问,对于其它的线程是不可见的。怎么理解这个定义呢?我们先来看一种场景:函数A处理完参数A,函数B要处理函数A处理过的参数A,那么函数A就要把参数A传递给函数B。如果函数C也要接着处理这个参数呢,函数D也要呢?那么参数A就要在这些函数之间不断地传递,这些函数生命时也要提前声明好参数。可想而知,如果有参数要在函数之间传递,那么函数会变得很复杂,调用函数也很复杂。有没有简便的办法呢?

其实我们在函数间传递参数,为的是要使这个参数对于需要的函数都可视,那么将它变成一个全局变量不就得了?可是变成全局变量的话,其它的线程就会访问到我这个全局变量,可能改变它的值,这不是本线程的本意,我只想一个人独占它。这时,我们就需要一种变量,对于本线程而言,它应该是一个全局变量,对于进程的其它线程而言,它又像是一个局部变量。这就是使用Local的一种场景了,Local就是这样一种变量。

如果在全局域定义了一个Local,那么这个local其实并不是一个全局变量,每个线程访问这个变量时,拿到的实际上都是本线程对应的Local。怎么实现这种效果呢?其实很简单,Local本身并不是一个变量,它还包含了一些操作。你可以这样理解,每个进程都有一个全局的字典,每个线程本身有自己的线程ID,进程的所有线程都可以访问这个全局的字典,那么它们把自己的线程ID当做字典的key,把需要存储的东西当做value,每个线程只能通过自己的key来访问这个字典,那么value本身就相当于一个线程独占的全局变量啦!是不是?每个线程都怪怪地拿属于自己的东西,一个全局的东西,这就相当于一个线程内部的全局变量。具体的代码实现有所区别,但大体上是这个思路。

class Local(object):
    __slots__ = (‘__storage__‘, ‘__ident_func__‘)

    def __init__(self):
        object.__setattr__(self, ‘__storage__‘, {})	# 存放东西的全局字典
        object.__setattr__(self, ‘__ident_func__‘, get_ident)	# 每个线程的key

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)    # 这里返回一个LocalProxy对象,LocalProxy是一个代理,代理Local对象。

    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)

  

Local怎么用?

伪代码如下

local = Local()
local.request = "i am a request"
local.response = "i am a response"

def work():
    local.request = xxxx    # 每个线程都只会访问到属于自己的request和response
    local.response = xxxx    # 就算改变response,也只是改变本线程的值

if __name__ == "__main__":
    for i in range(10):
        Thread(target=work).start()

 

通过声明一个全局的Local对象,然后像访问对象的属性一样访问你要保留的值。你可以这样理解,Local相当于一个字典,我要通过自己定义的key,来访问我需要的值,即调用local.key来获取值。这样使用起来其实很别扭,明明我是定义一个值,却变成像是访问一个对象的属性一样,写起来很奇怪,有时候也不好理解。能不能像定义一个全局变量一样,直接使用一个Local变量呢?

# 我想要这种效果
request = "i am a request"
response = "i am a response"

Local的__call__方法就是干这件事的,使用__call__方法,我们可以让一个Local变得看起来像一个全局变量。

# 你只需要调用Local对象的__call__方法
local = Local()
local.request = "i am a request"
my_request = local("request")    # 注意,这里传入的字符串需要和上面保存时的一致my_request  # "i am a request"

  

my_request现在等同于local.request,比起local.request,my_request是不是看起来更像一个全局变量?但记住,它是一个“线程独有的全局变量”。

LocalProxy是什么?

local相当于一个字典,local.x的x相当于key,而LocalProxy代管了这把key和local,我们只需访问LocaProxy本身,它自动用这把key去local字典查到值,返回给我们,这就是代理(proxy)



my_request实际上是一个LocalProxy,直接访问my_request,它是一个"i am a request"字符串。前面我们提到Local对象可以通过local.xxx=value来存储我需要的本地全局变量,这样的local对象看起来就像一个字典,可以存储任意的值。但是每次都通过local.xxx来获取我们想要的值太麻烦了,我们需要一个对象来帮我们完成这个重复性的动作,把key交给它,把字典交给它,我只要访问它,它就通过key去字典中查值,然后把值返回给我。这样子它对于我来说就像存储的值本身一样。这就是代理。

LocalProxy的原理就是这样,它帮我们干了到Local中查找值的方法,所以我们要把存储local.xxx时的“xxx”这把打开local的key告诉代理,然后把local本身也告诉代理,这样LocalProxy便有了钥匙,和要打开的门,自然他就可以把门里面的东西返回给我们了。从这个角度考虑,Local本身也可以看做是一个代理,它代理的是线程的全局变量,而它持有的key则是线程的id,它会通过id到全局的dict中查找本线程的全局变量,然后返回给我们。

class LocalProxy(object):
   __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)

    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, ‘__release_local__‘):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)      # 通过key(name)到字典(local)中获取value
        except AttributeError:
            raise RuntimeError(‘no object bound to %s‘ % self.__name__)

    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError(‘__dict__‘)

    def __repr__(self):
        try:
            obj = self._get_current_object()
        except RuntimeError:
            return ‘<%s unbound>‘ % self.__class__.__name__
        return repr(obj)

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    def __unicode__(self):
        try:
            return unicode(self._get_current_object())  # noqa
        except RuntimeError:
            return repr(self)

    def __dir__(self):
        try:
            return dir(self._get_current_object())
        except RuntimeError:
            return []

    def __getattr__(self, name):
        if name == ‘__members__‘:
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)  # 通过key(name)到字典(local)中去查找真正的value,并返回

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    if PY2:
        __getslice__ = lambda x, i, j: x._get_current_object()[i:j]

        def __setslice__(self, i, j, seq):
            self._get_current_object()[i:j] = seq

        def __delslice__(self, i, j):
            del self._get_current_object()[i:j]

    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)

 LocalProxy中有许多方法,这些方法都是LocalProxy本身实现的一些通用的方法,这些方法不是对本身的调用,而是对代理值的调用。

我们也可以不调用Local的__call__方法构造LocalProxy,可以直接通过LocalProxy的构造函数构造一个LocalProxy,实质上是一样的。

local = Local()
local.request = "request"
my_request = LocalProxy(local, "request")    # 第二个参数要和local.xxx的xxx相同

  

LocalStack是什么?

LocalStack和Local差不多,只不过Local像一个字典。LocalStack则是一个栈,存储数据的方式不太一样。可以认为它是一个线程独有的一个全局栈。使用它不用担心被进程的其它线程干扰。

class LocalStack(object):
    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

 

Local和线程安全的区别

Local并不代表着线程安全(Thread-Safe),线程安全更多的是强调多个线程访问同一个全局变量时的同步机制,而Local代表的全局变量时线程独占的,对于其他线程而言是不可见的,所以根本不存在线程安不安全的问题。Local永远只会被本线程操作,所以如果硬是要下一个定义,那么是线程安全的。

原文地址:https://www.cnblogs.com/flowell/p/local_local_proxy_local_stack.html

时间: 2024-08-30 18:23:33

Flask解析(一):Local、LocalStak、LocalProxy的相关文章

Flask 解析 Web 端 请求 数组

Web前台由 JavaScript 通过Ajax发送POST请求,当请求数据为数组时,Python Flask 做服务器时的解析如下: js: 1 var ids = []; 2 for (var i = 0; i < row.length; i++) { 3 ids.push(row[i].id); 4 } 5 console.log('del:' + ids); 6 $.ajax({ 7 method: 'POST', 8 url: '/dance_del_data', 9 dataType

flask中间件和LOCAL对象

中间件 我们知道 app.run()之后,会调用__call__()方法,看一下他的源码 def __call__(self, environ, start_response): """The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app` which can be wrapped to applying middleware

Flask之 Context local

对象在flask中是全局的,但是不同于通常所说的全局.这些对象实际上是对象的代理,是为本地准备的特殊上下文.很拗口,其实想理解很简单. 想象一下 上下文(context) 开始操作线程. 一个请求进来,web服务器决定去 生产一个新的线程(或者其他的,基本对象是能够处理的并发系统以外的其他线程). 当flask开始 内部请求 操作,它会认出当前的线程是活动的,并且绑定当前应用程序和WSGI环境到 这个 上下文(线程).他可以自动的在一个应用程序条用另外一个应用程序.

flask基础之LocalProxy代理对象(八)

前言 flask框架自带的代理对象有四个,分别是request,session,g和current_app,各自的含义我们在前面已经详细分析过.使用代理而不是显式的对象的主要目的在于这四个对象使用太过频繁,贯穿整个请求周期,显式传递很容易造成循环导入的问题,需要一个第三方的对象来进行解耦. 代理模式简介 代理模式是程序设计的一种结构模式,其目的是使调用者和执行者之间不发生直接的关系,而是使用一个代理人,这样调用者和执行者就进行了解耦,可以避免许多的问题. 代理模式使用的场景: 为真实目标类创建一

flask LOCAL线程隔离技术

from threading import Thread from werkzeug.local import Local local = Local()#实例化一个线程隔离对象 request = '123' class MyThread(Thread): def run(self): global request request = 'abc' print('子线程',request) mythread = MyThread() mythread.start() mythread.join(

Flask中处理依赖的技巧

Flask应用中通常会用工厂模式 来创建应用对象,这样方便配置和测试.现在我们就用实例来学习Flask的处理依赖 应用代码 # app/__init__.pyfrom flask import Flaskfrom flask_xxxext import Xxxfrom flask_yyyext import Yyy# ... 一些flask拓展xx = Xxx()yy = Yyy() def create_app(config=None): app = Flask(__name__) xx.in

Flask 基础知识一

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器. "微"(micro) 并不表示你需要把整个 Web 应用塞进单

Flask 上下文管理

flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递. flask是如何做的呢? 1.Python 实现的本地线程 保证即使是多个线程,自己的值也是互相隔离. import threading local_values = threading.local() def func(num): local_values.name = num import time time.sleep(1) print(local_values.name, threa

flask上下文管理

flask的上下文管理分应用上下文和请求上下文: 官方文档里是说先理解应用上下文比较好,不过我还是觉得反过来,从请求上下文开始记录比较合适,所以这篇先记录请求上下文. 那么问题来了,什么才是请求上下文: 通俗点说,其实上下文就像一个容器,包含了很多你需要的信息 request和session都属于请求上下文 request 针对的是http请求作为对象 session针对的是更多是用户信息作为对象 上下文的结构 说到上下文这个概念的数据结构,这里需要先知道,他是运用了一个Stack的栈结构,也就