
上下文
在
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 context
和request 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 1, in <module>
foo1()
File "<input>", line 2, in 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
,它的表现就像一个全局变量一样可以使用,但是它又不是一个真正的全局变量,因为一个多线程服务器中,不可能每个Thread
的request
都一样,那么如何让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 context
和request 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()
实例.
依次解释一下代码
__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
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)
使用 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
"""
解释一下 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
"""
所有这里的 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
"""
最后的 __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个不同的值它的作用是什么呢,这就要看
Flask
的global.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