Flask上下文管理
上下文管理
threading.local:为每一个线程开辟一个单独的内存空间来保存自己的值
import threading # class Foo(): # def __init__(self): # self.name=0 # # local_value=Foo() local_value=threading.local() def func(num): local_value.name=num import time time.sleep(1) print(local_value.name,threading.current_thread().name) for i in range(20): th=threading.Thread(target=func,args=(i,),name='线程%s'%i) th.start()
request:
情况一:单线程和单进程的情况下不会有问题,应为自己使用为完毕过后,就会自动的清空request。
情况二:单进程和多线程,threading.local对象
情况三:但进程和单线程多个协程,theading.local对象就会出问题。
解决方法:
以后不支持协程:就可以使用内置的threading.local对象
支持协程:自定义类似threading.local对象的功能支持协程
_thread.get_ident:获取线程的一个唯一标识
greenlet.getcurrent:获取协程额唯一标识
自定义(协程)
""" { 1368:{} } """ 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(): 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()
class Foo(object): def __init__(self): object.__setattr__(self,'storage',{}) def __setattr__(self, key, value): self.storage={'k1:v1'} print(key,value) def __getattr__(self, item): print(item) return 'df' obj=Foo() # obj.x = 123 print(obj)
""" { 1368:{} } """ import flask.globals 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(): def __init__(self): object.__setattr__(self,'__storage__',{}) object.__setattr__(self,'__ident_func__',get_ident) def __setattr__(self, name, value): ident=self.__ident_func__() storage=self.__storage__ try: storage[ident][name]=value except KeyError: storage[ident]={name:value} def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __delattr__(self, name): try: del self.__storage__[self.__ident_finc__()][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()
偏函数:functools模块
functools.partial:创建一个新的函数,主要是为了给原函数传入参数
import functools
def func(a1,a2):
print(a1+a2)
new_func=functools.partial(func,12) #
new_func(21)
面向对象的__add__方法补充:当把面向对象中的所有__函数__实现时,对象做任何操作时,都会执行其中对应的方法。
__add__:谁在前面就会调用谁的__add__方法
class Foo():
def __init__(self,num):
self.num=num
def __add__(self,other):
return Foo(self.num+other.num)
obj1=Foo(11)
obj2=Foo(22)
v=obj1+obj2
print(v)
拼接列表中的值:
itertools.chain:传入函数,产生一个新的函数对象的列表,主要帮助我们做列表元素的拼接
#####################示例##################
from itertools import chain
def a1(x):
return x+1
func_list=[a1,lambda x:x+1]
def a2(y):
return y+10
func_list_1=chain([a2],func_list)
for func in func_list_1:
print(func)
#示例二
v1=[1,2,3]
v2=[4,5,6]
for l in chain(v1,v2): # chain 主要是做列表的拼接
print(l)
Flask 上下文管理
- 与django相比是两种不同的实现方式。
- django/tornado是通过传参数形式
- flask是通过上下文管理
两种都可以实现,只不过试下方式不一样。
- 上下文管理:
- threading.local/Local类,其中创建了一个字典{greelet做唯一标识:存数据} 保证数据隔离
- 请求进来:
- 请求相关所有数据封装到了RequestContext中。
- 再讲RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
- 使用,调用request
- 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的方法
- 函数
- 通过LocalStack去Local中获取值。
- 请求终止
- 通过LocalStack的pop方法 Local中将值异常。
请求上下文:
request:是LocalProxy对象
这个对象实例化事传入了一个函数,还有一个request参数
以后执行函数partail(_Lookup_req_object,'request')时,自动传递request参数
目标:去Local中获取ctx,然后再在ctx中获取request
ctx.push:里面做的事,将ctx通过LocalStack添加到Local中
session:经过push之后,session里面已经有值了,seif.session帮助我获取session信息
应用上下文:
AppContext里面封装了app对象
app.context:创建了app对象
app.app_ctx_global_class:相当于一个全局变量
app和g
g:每个请求周期都会创建一个用于在请求周期中传递值的一个容器。只有一次生命周期可用,因为一次请求周期后第二次就会重新创建
print(g):执行g的实例对象
请求到来 ,有人来访问:
# 将请求相关的数据environ封装到了RequestContext对象中
# 再讲对象封装到local中(每个线程/每个协程独立空间存储)
# ctx.app # 当前APP的名称
# ctx.request # Request对象(封装请求相关东西)
# ctx.session # 空
_request_ctx_stack.local = {
唯一标识:{
"stack":[ctx, ]
},
唯一标识:{
"stack":[ctx, ]
},
}
# app_ctx = AppContext对象
# app_ctx.app
# app_ctx.g
_app_ctx_stack.local = {
唯一标识:{
"stack":[app_ctx, ]
},
唯一标识:{
"stack":[app_ctx, ]
},
}
使用:print打印request,session.g,current_app的时候,都会执行他们相对应的对象的__str__
他们之间不同的是偏函数不一样
from flask import request,session,g,current_app print(request,session,g,current_app) 都会执行相应LocalProxy对象的 __str__ current_app = LocalProxy(_find_app) 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'))
终止,全部pop
武sir飙车时间:
如果他是多线程的时候是如何实现的。
司机飙车flask的local中保存数据时,使用列表创建出来的栈。为什么用栈?
- 如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。 - 写脚本获取app信息时,可能存在app上下文嵌套关系。 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 app2 = Flask('app02') app2.debug = True # 用户/密码/邮箱 # app_ctx = AppContext(self): # app_ctx.app # app_ctx.g 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.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.config['DEBUG']) print(current_app.config['DEBUG'])
多app应用:不仅可以使用蓝图进行分发,还可以使用app进行分发
DispatchMiddleware:可以进行路由分发。
在这里面没有app.run,直接可以run_simple启动
from werkzeug.wsgi import DispatcherMiddleware from werkzeug.serving import run_simple from flask import Flask, current_app app1 = Flask('app01') app2 = Flask('app02') @app1.route('/index') def index(): return "app01" @app2.route('/index2') def index2(): return "app2" # http://www.oldboyedu.com/index # http://www.oldboyedu.com/sec/index2 dm = DispatcherMiddleware(app1, { '/sec': app2, }) if __name__ == "__main__": run_simple('localhost', 5000, dm)
问题:web访问多app,上线问管理是如何实现的
请求进来,为栈添加的还是一个值
问题:为什么使用栈,离线脚本
如果写web程序,或者web运行环境:栈中永远保存一条数据(这个可以不使用栈)
写脚本获取app信息的时候,可能会存在上下文嵌套关系。这时有可能要用到栈
问题:Web访问多app应用时,上下文管理是如何实现?
补充:
遇到with就会执行__enter__方法,这个方法返回值,as后面那个值就是返回值
当指执行完毕之后 ,自动调用类的__exit__方法
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 app2 = Flask('app02') app2.debug = True # 用户/密码/邮箱 # app_ctx = AppContext(self): # app_ctx.app # app_ctx.g with app1.app_context(): # # __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local print(_app_ctx_stack._local.__storage__) print(current_app.config['DEBUG']) with app1.app_context(): print(_app_ctx_stack._local.__storage__) print(current_app.config['DEBUG']) print(current_app.config['DEBUG']) with app1.app_context(): print(_app_ctx_stack._local.__storage__) print(current_app.config['DEBUG']) with app1.app_context(): print(_app_ctx_stack._local.__storage__) print(current_app.config['DEBUG'])
二 数据库连接池
django的常用数据库:
ORM:django默认的数据库
pymysql模块:导入mysql数据库,python2和python3版本都有这个模块
MySQLdb:一样,也是导入mysql数据库,这个模块只有python2版本才有
flask/其他:
pymysql:导mysql数据库
MySQLdb:导入mysql数据库
SQLAchemy:
是ORM的一款框架,可以导入mysql数据库(pymysql / MySQLdb)
原生SQL:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): import pymysql CONN = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8') cursor = CONN.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() print(result) return "Hello World" if __name__ == '__main__': app.run()
from flask import Flask app = Flask(__name__) import pymysql CONN = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8') @app.route("/") def hello(): cursor = CONN.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() print(result) return "Hello World" if __name__ == '__main__': app.run() ######加锁######################### from flask import Flask import threading app = Flask(__name__) import pymysql CONN = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8') @app.route("/") def hello(): with threading.Lock(): cursor = CONN.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() print(result) return "Hello World" if __name__ == '__main__': app.run()
解决问题:
不能为每一个用户创建一个链接
创建一定数量的连接池,只要连接池有空的,才会进来,不然就会等着。
使用UBDtils模块:
下载网站:https://pypi.python.org/pypi/DBUtils
pip install UBDtils
如果安装到虚拟环境下面,需要先切换到虚拟环境下面
使用:
模式一:
为每一个线程创建一个链接,即使县城调用close方法,也不会关闭掉
占用连接池不放,浪费了连接池
import pymysql from DBUtils.PersistentDB import PersistentDB POOL = PersistentDB( creator=pymysql, # 使用链接数据库的模块 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always closeable=False, # 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接) threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置 host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) def func(): conn = POOL.connection(shareable=False) cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() cursor.close() conn.close() func()
模式二:
设置最大限制创建连接池的数量,进来一个线程,就创建一个连接池
如果超过连接池的限制数量,就会在那里等着有空的连接池释放出来才能够下一个线程链接进去
import time import pymysql import threading from DBUtils.PooledDB import PooledDB, SharedDBConnection POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) def func(): # 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常 # 否则 # 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。 # 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。 # 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。 # 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。 conn = POOL.connection() # print(th, '链接被拿走了', conn1._con) # print(th, '池子里目前有', pool._idle_cache, '\r\n') cursor = conn.cursor() cursor.execute('select * from tb1') result = cursor.fetchall() conn.close() func()
工作实用
import pymysql from DBUtils.PooledDB import PooledDB POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123', database='pooldb', charset='utf8' ) """ class SQLHelper(object): @staticmethod def fetch_one(sql,args): conn = POOL.connection() cursor = conn.cursor() cursor.execute(sql, args) result = cursor.fetchone() conn.close() return result @staticmethod def fetch_all(self,sql,args): conn = POOL.connection() cursor = conn.cursor() cursor.execute(sql, args) result = cursor.fetchone() conn.close() return result # 以后使用: result = SQLHelper.fetch_one('select * from xxx',[]) print(result) """
三、信号
什么是信号:signal 一种处理异步时间的方法。信号是POSIX系统的信号,由硬件或软件触发,再有操作系统内核发给应用程序的中断形式。POSIX由一系列的信号集。
什么是信号量:semaphore 一种进程同步的机制。信号量是POSIX进程间通信的工具,在它上面定义了一系列操作原语,简单地讲它可以在进程间进行通信。
flask自己没有信号,要依赖blinker这个模块 安装 pip install blinker
内置信号:
request_started = _signals.signal('request-started') # 请求到来前执行 request_finished = _signals.signal('request-finished') # 请求结束后执行 before_render_template = _signals.signal('before-render-template') # 模板渲染前执行 template_rendered = _signals.signal('template-rendered') # 模板渲染后执行 got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行 request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否) appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 请求上下文执行完毕后自动执行(无论成功与否) appcontext_pushed = _signals.signal('appcontext-pushed') # 请求上下文push时执行 appcontext_popped = _signals.signal('appcontext-popped') # 请求上下文pop时执行 message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发
信号放哪里了:放在了signals文件里面
signals.request_started.conncet(函数名) :注册函数
等待请求的到来
在视图函数执行之前触法信号的
是怎么触发信号的?
signals.request_started.send:方法触发信号
send是在full_dispatch_request方法中执行的
1 a. before_first_request 2 b. 触发 request_started 信号 3 c. before_request 4 d. 模板渲染 5 渲染前的信号 before_render_template.send(app, template=template, context=context) 6 rv = template.render(context) # 模板渲染 7 渲染后的信号 template_rendered.send(app, template=template, context=context) 8 e. after_request 9 f. session.save_session() 10 g. 触发 request_finished信号 11 12 如果上述过程出错: 13 触发错误处理信号 got_request_exception.send(self, exception=e) 14 15 h. 触发信号 request_tearing_down 16 17 由信号引发的源码流程:找扩展点
浙公网安备 33010602011771号