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,用代理去拿
posted @ 2022-03-22 17:28  新入世界的小白  阅读(59)  评论(0编辑  收藏  举报