flask你一定要知道的上下文管理机制
前引
在了解flask上下文管理机制之前,先来一波必知必会的知识点。
面向对象双下方法
首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__、__getattr__系列、__getitem__系列。
__call__
这个方法相信大家并不陌生,在单例模式中,我们可能用到过,除此之外,还想就没有在什么特殊场景中用到了。我们往往忽视了它一个很特殊的用法:对象object+()或者类Foo()+()这种很特殊的用法。在Flask上下文管理中,入口就是使用了这种方式。
__getitem__系列
使用这个系列的方法时,我们最大的印象就是调用对象的属性可以像字典取值一样使用中括号([])。使用中括号对对象中的属性进行取值、赋值或者删除时,会自动触发对应的__getitem__、__setitem__、__delitem__方法。
class Foo(object): def __init__(self):
self.name = "boo" def __getitem__(self, item):
print("调用__getitem__了")
if item in self.__dict__:
return self.__dict__[item] def __setitem__(self, key, value):
print("调用__setitem__方法了")
self.__dict__[key] = value def __delitem__(self, key):
print("调用__delitem__")
del self.__dict__[key] foo = Foo()
ret = foo["name"]
# print(ret) # 输出 调用__getitem__了 boo
foo["age"] = 18
# print(foo["age"]) # 输出 调用__setitem__方法了 调用__getitem__了 18
del foo["age"] # 输出 调用__delitem__
__item__系列
__getattr__系列
使用对象取值、赋值或者删除时,会默认的调用对应的__getattr__、__setattr__、__delattr__方法。
对象取值时,取值的顺序为:先从__getattribute__中找,第二步从对象的属性中找,第三步从当前类中找,第四步从父类中找,第五步从__getattr__中找,如果没有,直接抛出异常。
class Foo(object): def __init__(self):
self.name = "boo" def __getattr__(self, item):
print("调用__getattr__了") def __setattr__(self, key, value):
print("调用__setattr__方法了") def __delattr__(self, item):
print("调用__delattr__") foo = Foo()
ret = foo.xxx # 输出 调用__getattr__了
foo.age = 18 # 调用__setattr__方法了
del foo.age # 输出 调用__delattr__
__attr__系列
偏函数
再来说说Python中的偏函数
python中有一个小工具包functools,这个包中包含了几个在很有用的小功能,比如:wraps:在使用装饰器时,使用这个方法可以保护函数的元信息。reduce:一个合并序列项为一个单一值的小方法。还有一个就是偏函数: partial
一句话来总结partial的作用,固定函数中的一些参数,返回一个新的函数,方便调用
from functools import partial class Foo(object): def __init__(self):
self.request = "request"
self.session = "session" foo = Foo() def func(args):
return getattr(foo,args) re_func = partial(func,'request')
se_func = partial(func,'session') print(re_func())
来一个复杂点的偏函数的用法
仿照flask来实现一个更复杂的
from functools import partial class HttpRequest(object): def __init__(self):
self.method = "GET"
self.body = b"name=abc@age=123" class Foo(object): def __init__(self):
self.request = HttpRequest()
self.session = {"login":True,"is_super":False} foo = Foo() def func(args):
return getattr(foo,args) re_func = partial(func,'request')
se_func = partial(func,'session') class LocalProxy(object): def __init__(self,local):
self._local = local def _get_current_object(self):
return self._local() def __getitem__(self, item):
return getattr(self._get_current_object(),item) request = LocalProxy(re_func)
ret = request._get_current_object().method
print(ret) ret = request['method']
print(ret) session = LocalProxy(se_func)
print(session._get_current_object())
仿flask版
threading.local
再来说一说threading.local方法
在多线程中,同一个进程中的多个线程是共享一个内存地址的,多个线程操作数据时,就会造成数据的不安全,所以我们就要加锁。但是,对于一些变量,如果仅仅只在本线程中使用,怎么办?
方法一,可以通过全局的字典,key为当前线程的线程ID,value为具体的值。
方法二,使用threading.local方法
threading.local 在多线程操作时,为每一个线程创建一个值,使得线程之间各自操作自己 的值,互不影响。
import time
import threading local = threading.local() def func(n):
local.val = n
time.sleep(5)
print(n) for i in range(10):
t = threading.Thread(target=func,args=(i,))
t.start() # 结果输出 0--9
threading.local示例
自定义使用threading.local的功能
import time
import threading
# from threading import current_thread as getcurrent
from greenlet import getcurrent class Local(object): def __init__(self):
object.__setattr__(self,"_storage",{}) def __setattr__(self, key, value): # ident = threading.get_ident()
ident = getcurrent() # 定制粒度更细的
if ident in self._storage:
self._storage[ident][key] = value
else:
self._storage[ident] = {key:value} def __getattr__(self, item):
# ident = threading.get_ident()
ident = getcurrent()
return self._storage[ident][item] local = Local() def func(n):
local.val = n
time.sleep(2)
print(local.val) for i in range(10):
t = threading.Thread(target=func,args=(i,))
t.start()
自定制更细粒度的threadinglocal
仿照flask用栈来实现自定义threading.local的存取
from greenlet import getcurrent class Local(object): def __init__(self):
object.__setattr__(self,"_storage",{}) def __setattr__(self, key, value): # ident = threading.get_ident()
ident = getcurrent() # 定制粒度更细的
if ident in self._storage:
self._storage[ident][key] = value
else:
self._storage[ident] = {key:value} def __getattr__(self, item):
# ident = threading.get_ident()
ident = getcurrent()
return self._storage[ident][item] class LocalStack(object): def __init__(self):
self.local = Local() def push(self,item):
self.local.stack = []
self.local.stack.append(item) def pop(self):
return self.local.stack.pop() def top(self):
return self.local.stack[-1] _local_stack = LocalStack()
_local_stack.push(55)
print(_local_stack.top()) # 取栈顶元素
利用栈
预热完毕,来一波真正的操作,不过在正戏上演之前,先来提一嘴,flask与其他python框架比如(Django、tornado)在整个请求生命周期中对于数据的管理机制的不同。django、tornado是通过传参的形式传递数据,而flask是通过其特有的上下文管理机制来管理数据的。
下面进入我们今天的正题----flask的上下文管理机制。在flask中,上下文管理机制分为两个大的部分:请求上下文和应用上下文。
flask的上下文管理机制
接下来,从以下三个大的方面分别探讨flask的两大上下文管理机制。
- 方面一:请求进来时
- 方面二:视图函数
- 方面三:请求结束前
先来一个最简单的flask版的Hello World
from flask import Flask app = Flask(__name__) @app.route('/')
def index():
return "Hello World" if __name__ == '__main__':
app.run()
Flask版Hello World
启动一个flask项目时,会先执行app.run()方法,这是整个项目的入口,执行run方法时,接着执行werkzeug模块中的run_simple
werkzeug中触发调用了Flask的__call__方法
请求进来时
触发执行__call__方法,__call__方法的逻辑很简单,直接执行wsgi_app方法,将包含所有请求相关数据和一个响应函数传进去。
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>' 上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数: environ:一个包含所有HTTP请求信息的dict对象; start_response:一个发送HTTP响应的函数。 在application()函数中,调用: start_response('200 OK', [('Content-Type', 'text/html')])
符合wsgi协议标准的函数
备注:__call__是一个符合wsgi标准的函数
执行wsgi_app方法
def wsgi_app(self, environ, start_response): ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
wsgi_app
第一步先执行了一个request_context的方法,将environ传进去,最后返回一个RequestContext类的对象,被ctx的变量接收(ctx=request_context(environ))
def request_context(self, environ):
"""Create a :class:`~flask.ctx.RequestContext` representing a
WSGI environment. Use a ``with`` block to push the context,
which will make :data:`request` point at this request. See :doc:`/reqcontext`. Typically you should not call this from your own code. A request
context is automatically pushed by the :meth:`wsgi_app` when
handling a request. Use :meth:`test_request_context` to create
an environment and context instead of this method. :param environ: a WSGI environment
"""
return RequestContext(self, environ)
request_context方法
这个ctx对象在初始化时,赋了两个非常有用的属性,一个是request,一个是session
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
这两个属性中request是一个Request()对象,这个对象就是我们在flask中使用的request对象,为我们提供了很多便捷的属性和方法,比如:request.method、request.form、request.args等等,另一个属性是session,初始为None。
紧接着执行ctx.push()方法,这个方法中,在执行请求上下文对象ctx之前先实例化了一个app_context对象,先执行了app_context的push方法,然后才执行_request_ctx_stack对象中的top和_request_ctx_stack.push(self),最后对ctx中的session进行处理。
所以,flask中的应用上下文发生在请求上下文之前。
def push(self): top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc) # 在执行request_context请求上下文的push方法时,先执行了app_context应用上下文的push方法
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None) if hasattr(sys, 'exc_clear'):
sys.exc_clear() # 然后执行请求上下文对象中LocalStack对象的push方法
_request_ctx_stack.push(self) # 最后处理session
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(
self.app, self.request
) if self.session is None:
self.session = session_interface.make_null_session(self.app)
在ctx.push方法的执行逻辑
但是我们先说请求上下文,在处理完应用上下文的push方法后,紧接着执行了_request_ctx_stack对象的两个方法。
而这个_request_ctx_stack是LocalStack这个类的对象。_request_ctx_stack = LocalStack()
LocalStack有没有很眼熟,没错,flask内部使用的机制就是类似于我们上文中自定义的LocalStack的机制,实例化过程中使用了面向对象中的组合概念,self._local = Local(),然后在自身又实现了push、pop、top方法,这三个方法中都是通过反射从Local类的实例化对象中对一个stack属性进行append、pop、[-1]的操作,所以,Local对象中的stack属性对应的值一定是一个类似于列表的东西。通过对列表的操作,实现一个类似于栈的存取。
接着聊聊这个Local类,在实例化时,会对每个对象生成一个storage的空字典。我们翻遍整个Local类的源码,发现内部并没有实现一个叫stack的方法或者属性,但是上面我们提到了LocalStack对象会对Local对象中的一个叫stack的东西进行一系列操作。找不到不会报错吗?
这就是flask的巧妙之处,通过类的一些魔法方法巧妙的实现了相应的处理。在前引中,提到如果对象中没有某个属性,取值时,最终会执行类中的__getattr__方法,然后再做后续的异常处理,flask将所有的对应逻辑都实现在了类的__getattr__方法中,将每一个线程存储到字典中,在请求进来时,将每一个对应的请求ctx存在一个列表中,使用时直接调用,而不是通过传参的形式,更体现出了flask框架的轻量级。
处理完_request_ctx_stack后,就该处理session了。
在flask中,处理session时,非常的巧妙,完美的遵循了开闭原则,会先执行session_interface对象的open_session方法,在这个方法中,会先从用户请求的cookie中获取sessionid,获取该用户之前设置的session值,然后将值赋值到ctx.session中。
处理完session后,ctx.push方法就执行完了,返回到最开始的app.wsgi_app方法中,执行完push方法后,接着执行full_dispatch_request方法,从这个名字中我们也能猜到,这个方法只要是负责请求的分发。
def full_dispatch_request(self): self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
full_dispath_request方法
在full_dispatch_request方法中先执行preprocess_request方法,这个方法,会先执行所有被before_request装饰器装饰的函数,然后就通过路由的分发执行视图函数了(dispatch_request)
执行视图函数时
在执行视图函数之前,先执行了before_request,在执行我们的视图函数。
视图函数主要处理业务逻辑。在视图函数中可以调用request对象,进行取值,也可以调用session对象对session的存取。
在整个request的请求生命周期中,获取请求的数据直接调用request即可,对session进行操作直接调用session即可。request和session都是LocalProxy对象,借助偏函数的概念将对应的值传入_lookup_req_object函数。先从_request_ctx_stack(LocalStack)对象中获取ctx(请求上下文对象),再通过反射分别获取request和session属性。整个过程中LocalStack扮演了一个全局仓库的角色,请求进来将数据存取,需要时即去即用。所以,flask实现了在整个请求的生命周期中哪儿需要就直接调用的特色。
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
请求结束前
视图函数执行完后,dispatch_request执行结束,执行full_dispatch_request方法的返回值finalize_request方法。这个方法中,同样的,在返回响应之前,先执行所有被after_request装饰器装饰的函数。
---->finalize_request ------> process_response
def process_response(self, response): ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response)
return response
process_response
执行process_response过程中,执行完after_request后,然后,执行session的save_session方法。将内存中保存在ctx.session的值取到后,json.dumps()序列化后,写入响应的cookie中(set_cookie),最后返回响应。
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app) # If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(
app.session_cookie_name,
domain=domain,
path=path
) return # Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
response.vary.add('Cookie') if not self.should_set_cookie(app, session):
return httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session))
# set_cookie将session写入响应的cookie中
response.set_cookie(
app.session_cookie_name,
val,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite
)
save_session
返回响应后,自动的调用ctx.auto_pop(error),将Local中存储的ctx对象pop掉,整个请求结束。
请求上下文的执行流程:
应用上下文
与请求上下文类似,当请求进来时,先实例化一个AppContext对象app_ctx,在实例化的过程中,提供了两个有用的属性,一个是app,一个是g。self.app就是传入的全局的app对象,self.g是一个全局的存储值的对象。接着将这个app_ctx存放到LocalStack()。
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
视图函数中,我们就可以调用app对象和g对象,如果我们使用蓝图构建我们的项目时,在每一个直接引用app就会造成循环引用的异常,这时,应用上下文就会显得非常有用,我们可以直接调用current_app就可以在整个生命周期中使用我们的app对象了。比如使用我们的配置项:current_app.config
current_app = LocalProxy(_find_app)
最后,当视图函数执行结束后,从storage中pop掉app_ctx对象。
from flask import Flask,request,session,g app = Flask(__name__) # type:Flask @app.before_request
def auth_demo():
g.val = 123 @app.route('/')
def index():
print(g.val)
return "Hello World" if __name__ == '__main__':
app.run()
g的简单使用示例
总结:flask中的上下文管理机制分为请求上下文和应用上下文两大方面,通过上下文的管理机制,实现了即去即用的一个特色。
flask你一定要知道的上下文管理机制的更多相关文章
- Flask的上下文管理机制
前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...
- Flask上下文管理机制
前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...
- Flask上下文管理机制流程(源码剖析)
Flask请求上下文管理 1 偏函数 partial 使用该方式可以生成一个新函数 from functools import partial def mod( n, m ): return n % ...
- 20191125:Python中的上下文管理机制with
20191125:with上下文管理 with是一个上下文管理器,用于执行代码块所需要的运行的时候的上下文入口和出口.上下文管理器的典型用法包括保存和还原各种全局状态,锁定和解锁资源,关闭打开的文件等 ...
- Flask 上下文管理
为什么用threading.local? 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程 ...
- Flask之上下文管理机制
前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...
- Flask 上下文管理-- (session,request,current_app的传递)--类似本地线程实现,以及多app应用
Flask session,request,current_app的传递 请求上下文的作用 -- 封装请求相关得数据(request,session) 请求上下文 request session re ...
- Flask 之 上下文管理
Flask上下文管理 分类: 请求上下文管理 应用上下文管理 请求上下文管理 request a. 温大爷:wsig b. 赵毅: ctx = ReuqestContext(session,reque ...
- python2.7高级编程 笔记一(Python中的with语句与上下文管理器学习总结)
0.关于上下文管理器上下文管理器是可以在with语句中使用,拥有__enter__和__exit__方法的对象. with manager as var: do_something(var) 相当于以 ...
随机推荐
- delphi Timage 加上滚动条方法
elphi Timage 加上滚动条方法 1:将 Timage 放入 TScrollBox内,即 [1]设image1.parent:= ScrollBox1; [2]在Object Ins ...
- js写插件教程深入
原文地址:https://github.com/lianxiaozhuang/blog 转载请注明出处 js 写插件教程深入 1.介绍具有安全作用域的构造函数 function Fn(name){ t ...
- 【转】如何解决使用keil下载或者调试程序是提示的“Invalid ROM Table”信息!
在将Discovery的工程移植到Mini-STM32F4x9BI开发板时,使用ULINK2下载程序可能会出现如下图所示的“Invalid ROM Table”这个错误. 可能原因是使用Mini-ST ...
- Libre 6006 「网络流 24 题」试题库 / Luogu 2763 试题库问题 (网络流,最大流)
Libre 6006 「网络流 24 题」试题库 / Luogu 2763 试题库问题 (网络流,最大流) Description 问题描述: 假设一个试题库中有n道试题.每道试题都标明了所属类别.同 ...
- Oracle 11g DRCP配置与使用
Oracle 11g DRCP配置与使用Oracle 11g推出了驻留连接池(Database Resident Connection Pool)特性,提供了数据库层面上的连接池管理机制,为应对高并发 ...
- HDU 6181 第k短路
Two Paths Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 153428/153428 K (Java/Others)Total ...
- 检测传入字符串是否存在重复字符,返回boolean
检测传入字符串是否存在重复字符,返回boolean,比如"abc"返回true:"aac"返回false 这里提供两种思路: 第一种: import java. ...
- 弹指之间 -- Prerequisites
CHAPTER 1 吉他的分类 Electric Guitar Classic Guitar Folk Guitar CHAPTER 2 吉他各部名称 CHAPTER 3 选购吉他 琴颈弯曲程度 木头 ...
- 「Vue」watch基本用法
应用于监视路由地址改变,如有新地址(即路由地址改变)即执行自定义方法 methods: { itemShow() { this.$axios.get('item/item/'+this.id+'?to ...
- AngularJS 项目里使用echarts 2.0 实现地图功能
项目中有一页是显示全国地图, echarts官网的地图实例里,有一个模拟迁徙的实例,比较符合项目需求.所以大部分配置项是参考此实例. angular 就不过多介绍了, Google出品的mvc(或者说 ...