Flask - 上下文管理(核心)
参考
- http://flask.pocoo.org/docs/1.0/advanced_foreword/#thread-locals-in-flask
- https://zhuanlan.zhihu.com/p/33732859
- https://www.zhihu.com/question/25033592/answer/34449852
- https://www.zhihu.com/question/269905592/answer/364928400
- http://flask.pocoo.org/docs/1.0/appcontext/#purpose-of-the-context
- http://flask.pocoo.org/docs/1.0/reqcontext/
- http://flask.pocoo.org/docs/1.0/appcontext/#storing-data
其实看5和6,7就够了,因为是权威的文档
Flask的上下文管理很重要,留坑
1-5为预备知识,从6正式开始
现象:
Flask 从客户端收到请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象就是一个很好的例子,它封装了客户端发送的HTTP 请求。要想让视图函数能够访问请求对象,一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数。除了访问请求对象,如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问。
PS: 访问其他对象,例如请求对象
django/tornado是通过传参数形式,Flask是通过上下文管理。
问题:flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递。flask是如何做的呢?
虽然threading.local与这个实现没关系,不过要先了解threading.local对象才能理解Flask的上下文管
1. threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。
Flask的上下文管理借助了这个思想。Thread-local data is data whose values are thread specific
本地线程,保证即使是多个线程,自己的值也是互相隔离。
import threading
# class Foo(object):
# def __init__(self):
# self.name = 0
#
# local_values = Foo()
local_values = threading.local()
def func(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name)
for i in range(20):
th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
th.start()
如果不用threading.local(),那么
因为修改的值始终保存在同一个对象里面,sleep一秒后,全部线程执行完毕,值变成了最后的值19。
2. 单进程单线程多协程中,threading.local会出问题,因为协程数据都存在同一个线程里。
解决方法为:
单进程单线程多协程中,程序不再支持协程,就可以使用threading.local对象。
单进程单线程多协程中,程序想支持协程,那么自定义类似threading.local对象。如下所示
> 自定义类似threading.local对象,支持协程(Python中greenlet就是协程),原理是一个标识identification对应一个字典(存储数据的地方)
"""
{
identification:{k:v},一个标识对应一个字典,threading.local工作原理类似这样,看源码可知,
class _localimpl:
"""A class managing thread-local dicts"""
}
"""
import threading
try:
#获取标识identification
from greenlet import getcurrent as get_ident # 协程
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident # 线程
# 模拟threading.Local()且支持协程
class Local(object):
def __init__(self):
self.storage = {}
self.get_ident = get_ident
def set(self,k,v):
ident = self.get_ident()
origin = self.storage.get(ident)
if not origin:
origin = {k:v}
else:
origin[k] = v
self.storage[ident] = origin
def get(self,k):
ident = self.get_ident()
origin = self.storage.get(ident)
if not origin:
return None
return origin.get(k,None)
local_values = Local()
def task(num):
local_values.set('name',num)
import time
time.sleep(1)
print(local_values.get('name'), threading.current_thread().name)
for i in range(20):
th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
th.start()
3. 注意__setattr__的坑,为往后的点作铺垫.
http://www.cnblogs.com/allen2333/p/9019660.html
改正后,可以用__setattr__,getattr
import threading
try:
from greenlet import getcurrent as get_ident # 协程
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident # 线程
class Local(object):
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
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_values = Local()
def task(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name)
for i in range(20):
th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
th.start()
4. 上下文原理
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from functools import partial
from flask.globals import LocalStack, LocalProxy
ls = LocalStack()
class RequestContext(object):
def __init__(self, environ):
self.request = environ
def _lookup_req_object(name):
top = ls.top
if top is None:
raise RuntimeError(ls)
return getattr(top, name)
session = LocalProxy(partial(_lookup_req_object, 'request'))
ls.push(RequestContext('c1')) # 当请求进来时,放入
print(session) # 视图函数使用
print(session) # 视图函数使用
ls.pop() # 请求结束pop
ls.push(RequestContext('c2'))
print(session)
ls.push(RequestContext('c3'))
print(session)
5. Flask内部实现
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from greenlet import getcurrent as get_ident
def release_local(local):
local.__release_local__()
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
# self.__storage__ = {}
# self.__ident_func__ = get_ident
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
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 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
stc = LocalStack()
stc.push(123)
v = stc.pop()
print(v)
6. 大概流程(重要,去看源码理解)
6.1 借鉴threading.Local(线程临时存对象的地方),Flask自定义Local对象(Local, LocalStack, LocalProxy)。Local类似threading.Local,是临时存对象的地方,比后者多出协程支持。
上下文管理:
类似threading.local ,Flask自己实现 Local类(临时存对象的地方),其中创建了一个字典,{用greenlet协程做唯一标识:存数据} 保证数据隔离
请求进来时:
- 请求相关所有数据封装到了RequestContext中。ctx = 封装RequestContext(request,session)
- 再将ctx = RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
执行view function时,调用request:
-调用此类方法: request.method、print(request)、request + xxx。
from flask import request中查看request源码
_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, 'request'))
- request.method会执行LocalProxy中对应的方法(getattr) --> 对应的方法执行_get_current_object --> 偏函数(._lookup_req_object(), request)--> 通过LocalStack从Local中 --> 获取top = RequestContext --> ctx.request --> getattr --> ctx.request.method
请求终止时:- ctx.auto_pop()
- 通过LocalStack的pop方法,ctx从Local对象中移除。
session
- 过程一样,只是最后是ctx.session,相比较于ctx.request
7. 应用上下文和请求上下文什么关系?
from flask import Flask, request, g, globals
app = Flask('_name_'), app._call_, ctx.push()
在这两段代码进去看源码
globals.py
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
在app.__call__进去,ctx = self.request_context(environ),再从ctx.push()进去
top = _request_ctx_stack.top
...
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
# Before we push the request context we have to ensure that there
# is an application context.
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()
_request_ctx_stack.push(self)
所以应用上下文在每个请求上下文被push到__request_ctx_stack之前,自己被push到_app_ctx_stack中
8. request和g的区别
g,每个请求周期都会创建一个用于在请求周期中传递值的一个容器。
request(在请求上下文中,LocalStack -- > 存储在Local中)和g都是临时容器,用来存储stuff。但是在程序中为了不修改request(封装了请求的参数),所以用g(在应用上下文中初始化,LocalStack -- >存储在Local中)来代替request存储数据、对象。
_request_ctx_stack = LocalStack(),_app_ctx_stack = LocalStack(),两个是不同的LocalStack --> 不同的Local。PS:Local是临时存储stuff的地方。
from flask import Flask, request, g, globals
app = Flask('name'), app.call, ctx.push()
在这两段代码进去看源码
_request_ctx_stack.Local() = {
唯一标识ident: {
"stack":[request_ctx, ]
}
}
_app_ctx_stack.Local() = {
唯一标识ident: {
"stack":[app_ctx, ]
}
}
request_ctx = RequestContext(),app_ctx = AppContext()
9. from flask import request,session,g,current_app
参考6,print(request,session,g,current_app),都会执行相应LocalProxy对象的 str,也就是都是从LocalProxy出发
唯一不同的是传递的偏函数的参数不一样
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
current_app = LocalProxy(_find_app)
g = LocalProxy(partial(_lookup_app_object, 'g'))
10. 一个程序多线程有多少个Local? Web访问多app应用时,上下文管理是如何实现?
还是原来的流程,wsgi --> push到Local。都是只有两个Local,通过唯一标识存储context.
_request_ctx_stack.Local() = {
唯一标识ident: {
"stack":[request_ctx, ]
}
唯一标识ident: {
"stack":[request_ctx, ]
}
....
}
_app_ctx_stack.Local() = {
唯一标识ident: {
"stack":[app_ctx, ]
}
唯一标识ident: {
"stack":[app_ctx, ]
}
}
11. Flask的Local中保存数据时,使用列表创建出来的栈。为什么用栈?
"stack":[app_ctx, ]
如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。
写脚本获取app信息时,可能存在app上下文嵌套关系,栈可能有多条数据。
意味着栈是备用的!
例如写测试脚本时,获取app1, app2的信息,测试一下数据库是否正确连接
总结来说,为了部落!(多应用)
from flask import Flask, current_app, globals, _app_ctx_stack
app1 = Flask('app01')
app1.debug = False # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g
#RuntimeError: Working outside of application context.
#print(current_app.config['DEBUG'])
app2 = Flask('app02')
app2.debug = True # 用户/密码/邮箱
# app_ctx = AppContext(self):
# app_ctx.app
# app_ctx.g
# 写脚本的时候有上下文嵌套的写法。但是写网站的时候没可能有两个或以上app嵌套,堆栈里的stack永远放的是一个。"stack":[app_ctx, ]
with app1.app_context(): # __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
# {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
print(_app_ctx_stack._local.__storage__)
print(current_app)
print(current_app.config['DEBUG'])
# 写在里面,Local:{'stack' : [ctx1, ctx2]}有两个了!stack.top --> stack[-1] --> 还是拿栈顶的!
with app2.app_context():
# {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
print(_app_ctx_stack._local.__storage__)
print(current_app)
print(current_app.config['DEBUG'])
"""
with app2.app_context():
# {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
print(_app_ctx_stack._local.__storage__)
print(current_app)
print(current_app.config['DEBUG'])
"""
Flask - 上下文管理(核心)的更多相关文章
- Flask上下文管理、session原理和全局g对象
一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...
- Flask上下文管理
一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...
- Flask上下文管理机制
前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...
- Flask 上下文管理
为什么用threading.local? 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程 ...
- Flask上下文管理及源码刨析
基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...
- flask 上下文管理 &源码剖析
基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...
- Flask 上下文管理-- (session,request,current_app的传递)--类似本地线程实现,以及多app应用
Flask session,request,current_app的传递 请求上下文的作用 -- 封装请求相关得数据(request,session) 请求上下文 request session re ...
- Flask上下文管理机制流程(源码剖析)
Flask请求上下文管理 1 偏函数 partial 使用该方式可以生成一个新函数 from functools import partial def mod( n, m ): return n % ...
- python - Flask 上下文管理 流程
上下文管理: - 请求上下文 (ctx=RequestContext()) : request/session - App上下文 (app_ctx=AppContext()) : a ...
随机推荐
- lucky的时光助理-2017.02
好久没有更新了, 即便没有听众, 有些故事还是要说给另一个自己听! lucky小姐在这个月开始重新找工作了, 她想找一份自己喜欢的工作, 然后安安稳稳的沉寂下来,她说:她要学些东西,才不会让自己看上去 ...
- RabbitMQ连接池、生产者、消费者实例
1.本文分享RabbitMQ的工具类,经过实际项目长期测试,在此分享给发家,各位大神有什么建议请指正 !!! 2.下面是链接池主要代码: import java.util.HashMap; impor ...
- VS2017中遇到不存在从string到const char*的转换函数的解决方法
使用c_str()函数 c_str函数的返回值是const char*. c_str()函数返回一个指向正规C字符串的指针, 内容与本string串相同. 这是为了与c语言兼容,在c语言中没有stri ...
- Sudo临时提权配置
目录 Sudo临时提权配置 参考 Sudo简介 Sudo配置文件 Sudo配置语法 Sudo配置实例 Sudo日志记录 Sudo临时提权配置
- Windows server 2012 R2 服务器用户自动锁定
开启共享文件夹后,发现经常会自动锁定导致,共享用户无法正常访问共享文件夹(原因不明) 解决办法,打开本地安全策略 把账户锁定阈值改为0
- 说明与比较:new Vue() 和 export default {}
在生成.导出.导入.使用 Vue 组件的时候,像我这种新手就会常常被位于不同文件的 new Vue() 和 export default{}.它们含义到底是什么,又有什么异同呢? 首先,Vue 是什么 ...
- linux文本处理工具-1
文件内容: cat ,more,less 文件截取:head,tail 按列抽取:cut 排序和统计:sort,wc ----------------------------------------- ...
- ROM, RAM, NVRAM and Flash Memory on Cisco Routers
当谈到路由器有多少内存以及哪些内存做什么时,有时人们会感到困惑. 您应该熟悉4个内存术语,在升级路由器的IOS之前应检查其中2个. 这些是以下内容: ROM:ROM代表只读存储器. 它存储System ...
- Titer软件学习(Translation Initiation siTE detectoR)
Titer Source Codes lnk: https://github.com/zhangsaithu/titer 函数: collections.namedtuple()函数:https:// ...
- Codeforces Round #600 (Div. 2) - D. Harmonious Graph(并查集)
题意:对于一张图,如果$a$与$b$连通,则对于任意的$c(a<c<b)$都有$a$与$c$连通,则称该图为和谐图,现在给你一张图,问你最少添加多少条边使图变为和谐图. 思路:将一个连通块 ...