flask基础third,中间件,猴子补丁,蓝图,线程,flask源码
一、中间件(与Django中间件完全不一样)
在执行原来的app.wsgi_app之前可以加入自己的逻辑,在之后也可以加入自己的逻辑,ret = self.old_wsgi_app(environ, start_response)被称为中间件
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return 'Hello World!' # 模拟中间件 class Md(object): def __init__(self,old_wsgi_app): self.old_wsgi_app = old_wsgi_app def __call__(self, environ, start_response): print('执行之前') #这里可以加入自己的逻辑 ret = self.old_wsgi_app(environ, start_response) print('执行之后') #这里也可以加入自己的逻辑 return ret if __name__ == '__main__': #1我们发现当执行app.run方法的时候,最终执行run_simple,最后执行app(),也就是在执行app.__call__方法 #2 在__call__里面,执行的是self.wsgi_app().那我们希望在执行他本身的wsgi之前做点事情。 #3 所以我们先用Md类中__init__,保存之前的wsgi,然后我们用将app.wsgi转化成Md的对象。 #4 那执行新的的app.wsgi_app,就是执行Md的__call__方法。 #把原来的wsgi_app替换为自定义的, app.wsgi_app = Md(app.wsgi_app) app.run()
二、猴子补丁
只是一个概念,不属于任何包和模块。利用了python一切皆对象的理念,在程序运行过程中,动态修改方法
概念
class Monkey(): def play(self): print('猴子') class Dog(): def play(self): print('狗子') m=Monkey() #先实例化类 m.play() #结果为猴子 m.play=Dog().play m.play() #结果为狗子
用处:动态修改原来的方法或者是模块
这里有一个比较实用的例子, 很多用到import json, 后来发现ujson性能更高, 如果觉得把每个文件的import json改成import ujson as json成本较高, 或者说想测试一下ujson替换是否符合预期, 只需要在入口加上: 只需要在程序入口写如下代码即可:此时调用json方法实际上调用的是ujson方法。
import json import ujson def monkey_patch_json(): json.__name__ = 'ujson' json.dumps = ujson.dumps json.loads = ujson.loads monkey_patch_json() aa=json.dumps({'name':'lqz','age':19}) print(aa)
对比分析协程:单线程下实现并发
from gevent import monkey;monkey.patch_all() import gevent import time def eat(): print('food 1') time.sleep(2) #可以放置所有阻塞事件 print('food 2') def play(): print('play 1') time.sleep(1) #可以放置所有阻塞事件
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2]) #此时就管理两个事物,遇到io就自动切
print('主')
三、蓝图
没有蓝图之前前,都是单文件
对于蓝图可以分文件,分app,之前的请求扩展还是一样用(除before_first_request只在app上,蓝图上没有),只是在当前蓝图对象管理下的有效。
蓝图使用
第一步在app中注册蓝图,括号里是一个蓝图对象
app.register_blueprint(user.us)
第二步,在不同文件中注册路由时,直接使用蓝图对象注册,不在使用app注册,避免了循环导入的问题
@account.route('/login.html', methods=['GET', "POST"])
概念及使用:
先新建一个项目(如下面的中小型项目文件布局,那么此时就在pro_flask的init.py里面写注册蓝图的代码),里面有static包、templates包、views包以及一个和views同级别的run.py启动文件。views里面有init.py、user.py、order.py。
#views下init.py里面的代码 from flask import Flask app = Flask(__name__,template_folder='templates',static_folder='static',static_url_path='/static') #配置静态文件路径,就相当于暴露静态文件,前端可以直接访问 from . import user #必须要导入views里面的视图模块 from . import order #注册蓝图 app.register_blueprint(user.us) app.register_blueprint(order.ord) #order里面的代码 from flask import Blueprint ord = Blueprint('order',__name__) #以后再注册路由,使用蓝图对象,在视图init方法里已经注册了。APP对象的功能,蓝图就有什么功能 @ord.route('/order') #这里就不使用@app.route('/order'),而是使用蓝图对象 def user(): return 'order' #user里面的代码 from flask import Blueprint us = Blueprint('user',__name__) @us.route('/user') def user(): return 'user' #run里面就是一个启动文件,代码 from views import app if __name__ == '__main__': app.run()
中小型项目目录划分
项目名字
-pro_flask文件夹 (这个文件夹放置templates、statics、views)
-__init__.py
-templates (模板)
-login.html
-statics (静态文件)
-code.png
-views (视图文件)
-blog.py
-account.py
-user.py
-run.py (和pro_flask同级别,为启动文件)
大型项目(然后各个模块的init.py文件夹里面的代码指定暴露静态文件与上面一样,大型项目就是中小型项目组合起来的)
项目名
-pro_flask文件夹
-__init__.py
#此时大型项目里面的文件夹init.py from flask import Flask from .admin import admin from .web import web app=Flask(__name__) app.debug = True #此时的蓝图注册 app.register_blueprint(admin,url_prefix='/admin') app.register_blueprint(web,url_prefix='/web') #此时像上面一样指定url_prefix,访问蓝图的地址就是http://127.0.0.1:5000/admin/xxx
-web
-__init__.py
-static
-views.py
-templates
-admin
-templates
-static
-views.py
-__init__.py
-run.py
四、threading.local
1、不使用local,多线程下写同一个数据会错乱
from threading import Thread import time sss = -1 def task(arg): global lqz sss = arg time.sleep(2) print(sss) for i in range(10): t = Thread(target=task,args=(i,)) t.start() #此时同时开启10条线程,但是数据会错乱,所以输出结果是同时打印出10个9
2、使用local对象,多线程写同一数据不会错乱,因为每个线程操作自己的数据,存储方式为:{'线程id':{value:1},'线程id':{value:2}....}。所以不会错乱
from threading import Thread from threading import local import time from threading import get_ident sss = local() #此时运行就是{'线程id':{value:1},'线程id':{value:2}....} def task(arg): sss.value = arg time.sleep(2) print(sss.value) for i in range(10): t = Thread(target=task,args=(i,)) t.start() #此时多线程操作同一个数据,并不会乱,输入0-9的数字
3、自己定义一个类似local的东西,函数版本
from threading import get_ident,Thread import time storage = {} #{'线程id':{value:1},'线程id':{value:2}....} #设置线程id号 def set(k,v): ident = get_ident() if ident in storage: storage[ident][k] = v else: storage[ident] = {k:v} #取线程id号 def get(k): ident = get_ident() return storage[ident][k] #执行任务 def task(arg): set('val',arg) v = get('val') print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start() #也是打印0-9
4、自己定义一个类似local的东西,面向对象版本,就是将上面的函数封装成一个类
from threading import get_ident,Thread import time #定义类 class Local(object): storage = {} def set(self, k, v): ident = get_ident() if ident in Local.storage: Local.storage[ident][k] = v else: Local.storage[ident] = {k: v} def get(self, k): ident = get_ident() return Local.storage[ident][k] #实例化类 obj = Local() #创建任务 def task(arg): obj.set('val',arg) time.sleep(1) v = obj.get('val') print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
5、每次实例化得到一个local对象,用自己的字典存储(此时只支持线程)
from threading import get_ident,Thread import time class Local(object): def __init__(self): object.__setattr__(self,'storage',{}) #self.storage={} # 这种方式不能用 def __setattr__(self, k, v): ident = get_ident() if ident in self.storage: self.storage[ident][k] = v else: self.storage[ident] = {k: v} def __getattr__(self, k): ident = get_ident() return self.storage[ident][k] obj = Local() def task(arg): obj.val = arg time.sleep(1) print(obj.val) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
6、支持协程(flask框架里面内置,可以查看local源码)
五、flask源码上下文执行流程分析
补充:partial(),偏函数可以提前传参数,后面再调用就不用传之前传过的参数了。
请求上下文执行流程步骤:
-启动初始:有6个全局变量,顶格写的,只要flask启动,他就会执行,直到flask停掉才回收
-_request_ctx_stack:LocalStack()对象
-_app_ctx_stack:LocalStack()对象
-request : LocalProxy对象
-session : LocalProxy对象
-1 请求来了开始执行 app.__call__()方法,但实质是内部执行:self.wsgi_app(environ, start_response)
-2 wsgi_app()
-2.1 执行:ctx = self.request_context(environ):返回一个RequestContext对象,并且封装了request(当次请求的request对象),session
-2.2 执行:ctx.push():RequestContext对象的push方法
-2.2.1 push方法中中间位置有:_request_ctx_stack.push(self),self是ctx对象
-2.2.2 去_request_ctx_stack对象的类中找push方法(LocalStack中找push方法)
-2.2.3 push方法源码:
def push(self, obj):
#通过反射找self._local,在init实例化的时候生成的:self._local = Local()
#Local()flask封装的支持线程和协程的local对象
# 一开始取不到stack,返回None
rv = getattr(self._local, "stack", None)
if rv is None:
#走到这,self._local.stack=[],rv=self._local.stack
self._local.stack = rv = []
# 把ctx放到了列表中
#self._local={'线程id1':{'stack':[ctx,]},'线程id2':{'stack':[ctx,]},'线程id3':{'stack':[ctx,]}}
rv.append(obj)
return rv
-3 如果在视图函数中使用request对象,比如:print(request)
-3.1 会调用request对象的__str__方法,request类是:LocalProxy
-3.2 LocalProxy中的__str__方法:lambda x: str(x._get_current_object())
-3.2.1 内部执行self._get_current_object()
-3.2.2 _get_current_object()方法的源码如下:
def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
#self.__local() 在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
# 用了隐藏属性self.__local ,这个只有内部才能看到,因为是__方法,所以前面传入_LocalProxy__local,是类名__方法传入
#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
#这个地方的返回值就是request对象(当此请求的request,没有乱)
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
-3.2.3 _lookup_req_object函数源码如下:
def _lookup_req_object(name):
#name是'request'字符串
#top方法是把第二步中放入的ctx取出来,因为都在一个线程内,当前取到的就是当次请求的ctx对象
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
#通过反射,去ctx中把request对象返回
return getattr(top, name)
-3.2.4 所以:print(request) 实质上是在打印当此请求的request对象的__str__
-4 如果在视图函数中使用request对象,比如:print(request.method):实质上是取到当次请求的reuquest对象的method属性
-5 最终,请求结束执行: ctx.auto_pop(error),把ctx移除掉
其他的东西:
-session:
-请求来了opensession
-ctx.push()---->也就是RequestContext类的push方法的最后的地方:
if self.session is None:
#self是ctx,ctx中有个app就是flask对象, self.app.session_interface也就是它:SecureCookieSessionInterface()
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
#经过上面还是None的话,生成了个空session
self.session = session_interface.make_null_session(self.app)
-请求走了savesession
-response = self.full_dispatch_request() 方法内部:执行了before_first_request,before_request,视图函数,after_request,savesession
-self.full_dispatch_request()---->执行:self.finalize_request(rv)-----》self.process_response(response)----》最后: self.session_interface.save_session(self, ctx.session, response)
-请求扩展相关
before_first_request,before_request,after_request依次执行
-flask有一个请求上下文,一个应用上下文
-ctx:
-是:RequestContext对象:封装了request和session
-调用了:_request_ctx_stack.push(self)就是把:ctx放到了那个位置
-app_ctx:
-是:AppContext(self) 对象:封装了当前的app和g
-调用 _app_ctx_stack.push(self) 就是把:app_ctx放到了那个位置
-g是什么:
专门用来存储用户信息的g对象,g的全称的为global
g对象在一次请求中的所有的代码的地方,都是可以使用的
-代理模式
-request和session就是代理对象,用的就是代理模式LocalProxy,用代理去拿