Flask上下文

Flask上下文
10318

上下文

Flask0.9 之前,Flask 只有请求上下文request context 的概念.

在写Flask 程序时,可以直接调用request,session,g 等变量.比如:

from flask import Flask,request
app= Flask(__name__)

@app.route('/'):
 def index():
  username = request.args.get('username')
  return 'This is {}'.format(username)

这些变量看起来像全局变量,因为可以直接调用并返回值.但实际上这些变量并不是真正意义的全局变量.比如在多线程服务器中,多个线程同时处理不同的客户端发送的不同请求时,每个线程看到的request对象必然是不同的.

graph TB
A(client1)
B(client2)
C(client3)
D(web)
E(Thead_1)
E2(Thead_2)
E3(Thead_3)
A-.request_1.->E
B-.request_2.->E2
C-.request_3.->E3
E-->D
E2-->D
E3-->D

如果不设置全局变量,那么在编写代码时必须手动传递request变量到视图函数中.这就导致了每个视图函数中必须增加一个参数.除了访问请求对象,如果视图函数还需要处理session ,那就又需要传递一个参数,这样情况会变得很复杂.

为了避免大量可有可无的参数把视图函数弄的复杂,Flask 使用上下文来临时把某些对象变为全局可访问.

  • 在这里Flask 在设计时采取了线程隔离的思路,也就是说在一次请求的一个线程中可以将request 变成全局变量,但是仅限于请求的这个线程内部,不同线程使用线程标识符来区别,这样就不会影响其他线程的请求.Flask 实现线程隔离主要用到了werkzeug 中的两个类:Local和LocalProxy
graph TB
A(client1)
B(client2)
C(client3)
D(web)
E(Thead_1)
E2(Thead_2)
E3(Thead_3)
A-.request是Thread_1的全局变量.->E
B-.request是Thread_2的全局变量.->E2
C-.request是Thread_3的全局变量.->E3
E-->D
E2-->D
E3-->D

  • 实现了线程隔离后,为了在一个线程中更方便的处理这些变量,Flask 通过werkzeug.LocalStack实现了了一个堆栈结构.它的作用是用来存储一个环境,比如,某个线程得到一个请求,那么和这个请求的所有相关信息都会打包,打包形成的就是处理请求的一个环境.Flask 将这种环境称为请求上下文request context,之后Flask 会把这个request context 放置到堆栈结构中.一切完成后,就可以通过这个request context 获取相关对象并直接访问,比如current_app,request,session,g .还可以通过调用对象的方法或者属性来获取其他信息,比如request.method ,当请求结束后,请求上下文会被删除,堆栈结构重新等待新的请求上下文对象被push .

对于单一应用来说request context 就可以了,但是Flask 是支持多app 实例的.当一个应用的请求上下文环境中,需要嵌套处理另一个引用的相关操作时(这种情况更多的用于测试或者脚本易用flask_script 等等),请求上下文就不能很好的处理问题了,因为current_app 无法确定当前处理的是哪个应用,也就无从谈起request context 了,为了解决这个问题,Flask 把与应用相关的信息单独保存下来,形成了;一个application context 应用上下文对象.

application contextrequest context 类似,它可以单独使用,也可以结合request context 一起使用.Flask 在处理请求时会自动推送应用程序上下文.

1.什么是上下文

类似于中文的结合上下文理解,上下文指示了一个环境,语境,语义.在程序中代码执行到某一时刻时,根据之前代码所做的操作以及下文将要执行的逻辑,可以决定当前时刻下可以使用的变量,或者要完成的事情.

比如:

from flask import Flask,request
app= Flask(__name__)

@app.route('/'):
 def index()
  username = request.args.get('username')
  return 'This is {}'.format(username)

函数index()访问了request 这个外部变量.假设request 并不是全局变量,就需要去构造request 的值,否则程序无法运行.类似

>>> c = 1
>>> def foo():
...     return c
... 
>>> foo()
1
>>> def foo1():
...     return a
... 
>>> foo1()
Traceback (most recent call last):
File "<input>", line 1in <module>
 foo1()
File "<input>", line 2in foo1
 return a
NameError: name 'a' is not defined

但是Flask 实现了请求上下文,这里的request 就是一个全局变量,它是根据当前的环境生成 的.那么这个环境就可以称之为上下文.

结合上面的介绍可以知道,Flask 实现了2种上下文,即请求上下文(request context)和应用上下文(application context).Flask 中的上下文相当于一个容器,它保存了Flask 运行过程中的一些信息.

  • application 指的是app=Flask(__name__) 创建的app对象.
  • request 指的是每次的http 请求发生时,WSGIServer调用Flask.__call__()之后,在内部创建的Response 对象.
  • application 的生命周期大于request,一个application 存活期间,可以发出多次http request ,会产生多个request.

2.请求上下文

像上面的request ,它的表现就像一个全局变量一样可以使用,但是它又不是一个真正的全局变量,因为一个多线程服务器中,不可能每个Threadrequest 都一样,那么如何让request的行为像全局变量一样呢?既然和Thread 线程相关,那么它的表现就应该是对于每个Thread ,request 对象是它的全局变量.

graph TB
A(client1)
B(client2)
C(client3)
D(web)
E(Thead_1)
E2(Thead_2)
E3(Thead_3)
A-.request是Thread_1的全局变量.->E
B-.request是Thread_2的全局变量.->E2
C-.request是Thread_3的全局变量.->E3
E-->D
E2-->D
E3-->D

Python 的多线程模块中,有一个类似的概念threading.local ,它可以实现多线程中每个线程只使用自己的局部变量,而不影响其他线程,类似与:

import threading

# 创建一个全局的Local对象,它存储的是一个字典对象
local = threading.local()


# 创建一个函数
def func(var):
 local.tname = var
 print(local.tname)

if __name__ == '__main__':
 t1 = threading.Thread(target=func,args=('test1',))
 t2 = threading.Thread(target=func,args=('test2',))
 t1.start()
 t2.start()
 t1.join()
 t2.join()

运行后:

❯ python 线程访问局部变量.py
test1
test2

虽然local 是一个全局对象,但是由于是一个threading.local() 对象.所以每个子线程访问得到的局部变量是不同的.threading.local()内部保存是一个字典对象.实现了线程和值的对相应类似于

{
 'thread_1':{
     'tname':'test1'    
 },
  'thread_2':{
  'tname':'test2'
  }
}

Flask 中也实现了类似的对应.它依赖与werkzeug.Local .


这里先来看下Flask 中的request context 的定义,它被写入到了globals.py 文件中:

from functools import partial

from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

_request_ctx_err_msg='...'
_app_ctx_err_msg='...'

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 = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))

Flask 中提供的2种上下文:application contextrequest context ,在这里分别演化如下:

  • application context
    • current app
    • g
  • request context
    • request
    • session

他们的实现依赖于LocalProxy(),为了更好的使用内部变量,由生成了2个堆栈结构_request_ctx_stack_app_ctx_stack ,它依赖与LocalStack(),根据导入可以知道他们均来自于werkzeug.local

1.werkzeug.local

官网介绍

文档中直接写出了为什么要实现一个local 模块

The Python standard library has a concept called “thread locals” (or thread-local data). A thread local is a global object in which you can put stuff in and get back later in a thread-safe and thread-specific way. That means that whenever you set or get a value on a thread local object, the thread local object checks in which thread you are and retrieves the value corresponding to your thread (if one exists). So, you won’t accidentally get another thread’s data.
This approach, however, has a few disadvantages. For example, besides threads, there are other types of concurrency in Python. A very popular one is greenlets. Also, whether every request gets its own thread is not guaranteed in WSGI. It could be that a request is reusing a thread from a previous request, and hence data is left over in the thread local object.

以上文档解释了并发 的问题,多线程并不是唯一的方式,在Python 中还有greenlet协程.协程的特点就是一个线程执行,一个线程中可以存在多个协程,可以理解为:协程复用线程.对于WSGI 应用来说,如果每一个线程处理一个请求,那么threading.local 可以解决单线程访问局部变量,但是每一个协程去处理一个请求时,一个线程中就存在多个协程,threading.local 会造成多个请求之间数据的相互干扰.为了解决这个问题,werkzeug 实现了一个local模块.它包含了4个类:

  • Local:存储线程或协程的私有变量
  • LocalStack:堆栈数据结构
  • LocalProxy:负责把所有对自己的操作转发给内部的 Local 对象
  • LocalManager

源码文档:

# werkzeug.local
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident
        
  def release_local(local):
    local.__release_local__()


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 __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


class LocalManager(object):

    def __init__(self, locals=None, ident_func=None):
        if locals is None:
            self.locals = []
        elif isinstance(locals, Local):
            self.locals = [locals]
        else:
            self.locals = list(locals)
        if ident_func is not None:
            self.ident_func = ident_func
            for local in self.locals:
                object.__setattr__(local, "__ident_func__", ident_func)
        else:
            self.ident_func = get_ident

    def get_ident(self):

        return self.ident_func()

    def cleanup(self):
        """Manually clean up the data in the locals for this context.  Call
        this at the end of the request or use `make_middleware()`.
        """

        for local in self.locals:
            release_local(local)

    def make_middleware(self, app):
        """Wrap a WSGI application so that cleaning up happens after
        request end.
        """


        def application(environ, start_response):
            return ClosingIterator(app(environ, start_response), self.cleanup)

        return application

    def middleware(self, func):
        return update_wrapper(self.make_middleware(func), func)

    def __repr__(self):
        return "<%s storages: %d>" % (self.__class__.__name__, len(self.locals))


@implements_bool
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__)
        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)

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

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

先看Local

# werkzeug.local
# 尝试导入线程或协程
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident
        
  def release_local(local):
    local.__release_local__()


class Local(object):
    """
    class object:
    __slots__: Union[Text, Iterable[Text]]
    object.__slots__:这个类变量可以赋值为字符串,或可迭代对象,它会对已声明的变量保留空间,并阻止自动为每个实例创建__dict__和__weakref__
    >>> foo = Foo()
    >>> dir(foo)
    ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__ini
    t__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setat
    tr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'test']
    """

    __slots__ = ("__storage__""__ident_func__")

    def __init__(self):
        """
        __setattr__ ==> x.any =value 赋值语句时使用
      object.__setattr__(self, "__storage__", {})
      相当于: 
      localTest = Local()
      localTest.__storage__ = {}
      等于给 Local()实例添加一个属性 ,并且值为列表
      这里相当于为Local()实例添加了2个属性值
      Local().__storage__ = {}
      Local().__ident_func__ = get_ident
      get_ident 是线程的唯一标识符
        """

        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)

 """
 运算符重载
 __getattr__ ==> x.any 获取不存在的属性
 __setattr__ ==> x.any = value 属性赋值语句
 __delattr__ ==> del x.any 删除属性
 实现了访问Local()实例属性值的操作
 """
       
            
    def __getattr__(self, name):
        """
        storage={
         ident:{
          name:value
         }
        }
        return storage[ident][name]
        """

        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        """
        实现了一个字典对象保存如下:
        storage={
         ident:{
          name:value
         }
        }
        storage={
         线程唯一标识符:{
          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() 实例的数据都保存在__storage__ 属性中, 这个属性是一个嵌套的字典,它类似与:
local = Local()
local.__strorage__={
    ident:{                           # 线程或协程的唯一标识符
        name:value            # 线程或协程中的变量对应
    }
}
  • 每一个client  都会有唯一一个的一个线程或携程与之对应,这样就实现了线程(协程)隔离
graph LR
A(client1)
B(127.0.0.1:5000)
C(Thread1)
D(name:value)
A1(client2)
B1(127.0.0.1:5000)
C1(Thread2)
D1(name:value)

A-->B
A1-->B1
B-->C
B1-->C1
C-->A
C-->D
C1-->A1
C1-->D1

  • ident 是自动对应的当访问实例的属性时,就自动变成了访问内部的字典
local = Local()
local.__strorage__={
    ident:{                           # 线程或协程的唯一标识符
        name:value            # 线程或协程中的变量对应
    }
}
# ident 是唯一确定的,假设标识符是:
ident = 123
# 访问local.name相当于
local.name ==> Local().__getattr__(name)==>local.__storage__[ident][name]
# 自动变成访问内部字典
  • Local 类内部实现了__release_local__ ,用来清空当前线程(协程)的数据.
  • __call__ 返回一个LocalProxy 对象.

接下来看另外一个类LocalStack,它用来实现一个堆栈结构.

class LocalStack(object):
  """
  实例化一个 Local()对象
  """

    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)
"""
push,pop,top 实现了堆栈的操作.
"""

    def push(self, obj):
        """
        getattr(x,y)==>x.y getattr()能够不返回报错而返回一个默认值
        rv = Local().stack 有就返回,没有就返回None
        rv=Local().stack ==> 访问内部字典对象的值,==>Local().__storage__[ident][stack]
        初始为没有值,就指定一个空列表
        Local().stack = []
        push(a)=>Local().stack.append(a)
        """

        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:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """
        self._local.stack[-1] ==> Local().stack[-1]==>Local().__storage__[ident][stack][-1]
        top() 永远指向了最开始的元素
        """

        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None


localProxy:负责把所有对自己的操作转发给内部的 Local 对象,重点关注它的__call__函数,它会返回一个Local() 实例对象

lambda x: x
@implements_bool
class LocalProxy(object):
    """
    __slots__ 魔法属性
    """

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

    def __init__(self, local, name=None):
        """
        object.__setattr__(self, "_LocalProxy__local", local)表示会为实例创建一个属性值
        def local(name):
         return 1
        localproxy = LocalProxy(local,name)
        localproxy.__LocalProxy__local = local
        localproxy.__name__ = name
        """

        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):
        """
        这里有一个让人疑惑的参数. self.__local,其实这个代表了类的一个私有变量,
        当访问 self.__local ==> self.__LocalProxy__local,而 __LocalProxy__local
        恰好是上面实现的 object.__setattr__(self, "_LocalProxy__local", local)
        所以其实 self.__local ==> local==>等价于访问传递进入的 local 变量.
        这就实现了访问某个单一的 Local()
        """

        # Local,LocalStack 都实现了 __release_local__ 方法
        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__) 
            
    """
    实现访问不存在属性的操作
     localproxy = LocalProxy()
    self._get_current_oject().name ==> localproxy.
    """
        
   def __getattr__(self, name):
        if name == "__members__":
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

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

    def __delitem__(self, key):
        del self._get_current_object()[key]
            
 __call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
  
 def __call__(self,*args,**kwargs):
        def wrapper(*args,**kwargs):
            return self._get_current_object()(*args,**kwargs)
        return wrapper

重载了__call__ 后,访问LocalProxy() 相当于访问一个Local() 实例.


依次解释一下代码

  1. __slots__ = ("__local")魔法方法__slots__查看官网 它允许显式的声明属性并禁止创建__dict__和__weakref__ 用来节省空间,提升查询属性的速度.类似与:
>>> class Foo:
...     __slots__ = ('_local','test')
...     
... 
>>> dir(Foo)
['__class__''__delattr__''__dir__''__doc__''__eq__''__format__''__ge__''__getattribute__''__gt__''__hash__''__init__''__init_subclass__''__le__''__lt__''__module__''__ne__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__slots__''__str__''__subclasshook__''_local''test']
 
 # 这样就显式的创建属性 Foo._local, Foo.test
  1. object.__setattr__(self,'_LocalProxy__local',local) 属性拦截 ,有3个默认的属性拦截方法:
    • __getatt__:当用户访问一个根本不存在*(或者暂时不存在)* 的属性时,你可以通过这个魔法方法来定义类的行为,
    • __setattr__:它允许你自定义某个属性的赋值行为,不管这个属性存在与否.
    • __delattr__:删除某个不存在的属性
class Foo:
    def __setattr__(self,key,value):
        return value
        
    def __getattr__(self,key):
        if key == 'a':
            return 'hello'
        else:
            return 'world'
        
foo = Foo()
foo.a=1
print(foo.a)
  1. 使用object和self 可以达成相同的效果
class Foo:
    def __init__(self,x,y):
        object.__setattr__(self,'att_x',x)
        object.__setattr__(self,'att_y',y)
        
foo =Foo(1,2)
print(foo.att_x)
print(foo.att_y)
"""
1
2
"""

  1. 解释一下self.__lcoal 指向的值,在Python 中,默认的__ 双下划线表示是有属性,无法从外部直接访问,而且根据PEP8 会直接更名.类似与__span==>_classname_span ,其中classname 为当前类名.
class Test:
    def __init__(self):
        self.x = 10
        self._x = 20
        self.__x = 30
        
test = Test()
print(test.__dict__)
"""
{'x': 10, '_x': 20, '_Test__x': 30}
其中 test.__x 变成了 _Test__x  
"""

  1. 所有这里的self.__lcoal 要根据__init__ 中的定义一起来理解
class LocalProxy(object):
    
    __slots__ = ("__local""__dict__""__name__""__wrapped__")

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        
        
    def _get_current_object(self):

        if not hasattr(self.__local, "__release_local__"):
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)
                      
  """
  这里的 self.__local() 相当于访问 self._LocalProxy__lcoal ,而这正好是 init中定义好的变量,所以它指向了 传入的变量 local
  """

  1. 最后的__call__ 实现了一个闭包函数类似与
class Foo:
    def __call__(self):
        return '1'

class MyClass:
    def __init__(self,foo):
        object.__setattr__(self,'_MyClass__local',foo)
    
    def add(self):
        return self.__local
    
    __call__ = lambda x, *a,**kw:x.add()(*a,**kw)
foo = Foo()
mc =MyClass(foo)
print(mc())
"""
'1'
"""


看完代码后,来解释一下为什么会出现LocalProxy ,这主要是解决多个可调用对象的时候出现的问题.

Local 实现了线程之间数据隔离,LocalStack 实现了一个堆栈数据机构,需要处理的线程会被push 到堆栈中,使用完后会释放掉,这样,一个线程中存在多个数据,当访问这些数据时,如何才能访问到自己想要的数据,类似与:

from werkzeug.local import LocalStack,LocalProxy

user_stack = LocalStack()
user_stack.push({'name':'Jack'})
user_stack.push({'name':'Bob'})

def get_user():
    return user_stack.pop()
user = get_user()
print(user['name'])
print(user['name'])
"""
Bob
Bob
"""

如果使用LocalProxy ,会得到不同的值

from werkzeug.local import LocalStack,LocalProxy

user_stack = LocalStack()
user_stack.push({'name':'Jack'})
user_stack.push({'name':'Bob'})

def get_user():
    return user_stack.pop()
user = LocalProxy(get_user,'test')
print(user['name'])
print(user['name'])
"""
Bob
Jack
"""

使用了LocalProxy 后,它自动更新了user 的值,从而得到了不同的值.类似于:

from werkzeug.local import LocalStack,LocalProxy

user_stack = LocalStack()
# 查看 线程或协程 对应的字典
print(user_stack._local.__storage__)
# {}
user_stack.push({'name':'Jack'})
user_stack.push({'name':'Bob'})
# 查看 线程或协程 对应的字典
print(user_stack._local.__storage__)
# {<greenlet.greenlet object at 0x7f8e3434a5d0>: {'stack': [{'name': 'Jack'}, {'name': 'Bob'}]}}

def get_user():
    return user_stack.pop()
user = LocalProxy(get_user,'test')
print(user['name'])
# 查看 线程或协程 对应的字典
print(user_stack._local.__storage__)
# {<greenlet.greenlet object at 0x7f8e3434a5d0>: {'stack': [{'name': 'Jack'}]}}
print(user['name'])

堆栈结构是自动更新的,而使用LocalProxy 后可以连接到自动更新后的堆栈结构中.

user = LocalProxy(get_user,'test')
==> user._get_current_object()(get_user,'test')
==> 进行判断
       if not hasattr(self.__local, "__release_local__"):
            return self.__local()
==> 相当于判断
       if not hasattr(get_user, "__release_local__"):
            return get_user()
==>get_user()('test')


如上,访问两次 user['name'],相当于进行了2次的 get_user()赋值
==>user1=get_user()('test')
  user2=get_user()('test')
 user['name']
==>user1['name']
==>user2['name']
对比user = get_user(),可以获得2个不同的值

它的作用是什么呢,这就要看Flaskglobal.py

from functools import partial

from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

_request_ctx_err_msg='...'
_app_ctx_err_msg='...'

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 = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))

这里实现了一个request=LocalProxy(partial(_lookup_req_object,'request'))

partial阅读官方文档高级函数,它提供了一个类似下面的方法.

假如有如下函数

def multiply(x, y):
    return x * y

现在,如果像返回某个数的双倍,即:

>>> multiply(3, y=2)
6
>>> multiply(4, y=2)
8
>>> multiply(5, y=2)
10

上面的调用有写繁琐,每次都需要传入y=2,我们可以定义一个新的函数,把y=2 作为默认值,

def double(x, y=2):
    return multiply(x, y)
# 现在在调用这个函数

>>> double(3)
6
>>> double(4)
8
>>> double(5)
10

使用partial 可以完成如上的任务

from functools import partial

double = partial(multiply, y=2)

partial 接收函数 multiply 作为参数,固定 multiply 的参数 y=2,并返回一个新的函数给 double,这跟我们自己定义 double 函数的效果是一样的。

所以,简单而言,partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数.

所以上面的也很好理解:

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

_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, "request"))
# ==> request = LocalProxy(_lookup_req_object('request'))

典型的Flask结构:

from flask import Flask,request
app= Flask(__name__)

@app.route('/'):
    def index():
     username = request.args.get('username')
     return 'This is {}'.format(username)
    
@app.route('/login/') 
 def login():
        username = request.args.get('username')
     return 'This is {}'.format(username)

这样访问的是同一个request 对象,而访问到的网页是两个,获取到的值应该是不一样的.这是因为request 是一个LocalProxy 对象,它会更新LocalStack

request.args.get()
==> request1.args.get()
  request2.artgs.get()
==> 实现了 LocalStack的自动更新
from flask import Flask, request
from flask.globals import _app_ctx_stack, _request_ctx_stack

app = Flask(__name__)

# 堆栈结构
print(_app_ctx_stack._local.__storage__)
print(_request_ctx_stack._local.__storage__)


@app.route('/')
def index():
    username = request.args.get('username')
    print(_app_ctx_stack._local.__storage__)
    print(_request_ctx_stack._local.__storage__)
    return 'This is {}'.format(username)


@app.route('/login/')
def login():
    username = request.args.get('username')
    print(_app_ctx_stack._local.__storage__)
    print(_request_ctx_stack._local.__storage__)
    return 'This is {}'.format(username)


if __name__ == '__main__':
    app.run(debug=True)
    
"""
从浏览器访问:
http://127.0.0.1:5000/?username=python
http://127.0.0.1:5000/login/?username=python
得到:
{<greenlet.greenlet object at 0x7f4e6c24fd60>: {'stack': [<flask.ctx.AppContext object at 0x7f4e6c274950>]}}
{<greenlet.greenlet object at 0x7f4e6c24fd60>: {'stack': [<RequestContext 'http://127.0.0.1:5000/?username=python' [GET] of app>]}}
127.0.0.1  "GET /?username=python HTTP/1.1" 200 -
{<greenlet.greenlet object at 0x7f4e6c24ff70>: {'stack': [<flask.ctx.AppContext object at 0x7f4e6c274f90>]}}
{<greenlet.greenlet object at 0x7f4e6c24ff70>: {'stack': [<RequestContext 'http://127.0.0.1:5000/login/?username=python' [GET] of app>]}}
127.0.0.1  "GET /login/?username=python HTTP/1.1" 200 -
"""
    

以上实现了不同的上下文内容.

2.g全局变量

对于 g 这个上下文变量来说,其用途会更加广泛些。比如说如果对于某个请求,我们几个视图函数都需要用到一个前端传递过来的变量,那么就可以把它保存到 g 变量当中

g.name = request.args.get('name')

这样,其他的视图函数就可以在同一个请求中直接使用 g.name 来访问,而不用每次都调用 request 了。而这种特性往往和请求钩子相结合使用,可以极大的提高代码的简洁性

– END –


原文始发于微信公众号(Flask学习笔记):Flask上下文

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/36475.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!