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,用代理去拿