首页 > 编程语言 >flask源码解析

flask源码解析

时间:2023-04-07 19:55:47浏览次数:43  
标签:__ flask ctx self request 源码 environ 解析 local

flask源码解析

本篇主要是针对于以下一些问题进行源码剖析,并补充解释一些python语法的用法与应用场景。

  • flask生命周期流程
  • flask的request、session等都是导入进来的,也就意味着每次请求,我们所用的都是同一个request对象,它为什么能够按照同种方式取到自己request对象值呢

flask生命执行流程

app.run()

启动项目,使用app.run,app是Flask对象:

def run(self,host,post,debug,...):
	# 加载一些配置,启动服务
    ...
    # 用werkzeug开启web服务
    from werkzeug.serving import run_simple
	run_simple(t.cast(str, host), port, self, **options)

开启web服务

需要借助符合wsgi协议的web服务器能够开启web服务,django使用的是wsgiref,而flask用的是werkzeug,那么代码上如何实现wsgi服务呢。

wsgiref开启web

from wsgiref.simple_server import make_server

# mya 就等同于django
def mya(environ, start_response):
    #把environ包装成了request
    print(environ)
    start_response('200 OK', [('Content-Type', 'text/html')])
    if environ.get('PATH_INFO') == '/index':
        with open('index.html','rb') as f:
            data=f.read()

    elif environ.get('PATH_INFO') == '/login':
        with open('login.html', 'rb') as f:
            data = f.read()
    else:
        data=b'<h1>Hello, web!</h1>'
    return [data]  # 做成了response

if __name__ == '__main__':
    myserver = make_server('', 8008, mya)
    print('监听8010')
    myserver.serve_forever()

werkzeug开启web

from werkzeug.wrappers import Request, Response


@Request.application
def hello(request):
    return Response('Hello World!')


if __name__ == '__main__':
    from werkzeug.serving import run_simple

    run_simple('localhost', 4000, hello)

将函数用Request.application装饰为视图函数,并返回Response对象,那么run_simple函数中传入这个函数hello,并监听指定端口,每次http请求来时,就相当于执行一次函数hello,因为hello函数返回了Response对象,所以可以被顺利的处理成http响应返回给网页。

run_simple

所以了解了run_simple()的运行机制,那么我们就可以解释app.run中的run_simple(t.cast(str, host), port, self, **options)

因为self是app本身,所以run_simple监听http请求后会执行的是app(),对象调用时会触发__call__方法,它之中执行了return self.wsgi_app(environ, start_response)

根据我们刚才对werkzeug开启web的解释,这里一定会返回一个Response对象。而在wsgi_app内部是关于flask框架为我们做了一些功能封装。

app.wsgi_app

def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
        ctx = self.request_context(environ)
        error: t.Optional[BaseException] = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if "werkzeug.debug.preserve_context" in environ:
                environ["werkzeug.debug.preserve_context"](_cv_app.get())
                environ["werkzeug.debug.preserve_context"](_cv_request.get())

            if error is not None and self.should_ignore_error(error):
                error = None

            ctx.pop(error)

不难发现,这段代码的核心步骤为:

ctx = self.request_context(environ)
ctx.push()
response = self.full_dispatch_request()
return response(environ, start_response)

ctx为context的缩写,表示上下文,这其实是为什么不同请求处理中能用同一个request的同样方式还能准确使用自己数据的关键。

self.request_context(environ)会返回一个RequestContext(self, environ)对象赋值给ctx。

这个ctx是根据本次请求的environ字典(werkzurg帮我们将请求处理成了字典)生成的对象,其初始化时就将请求对象做了很多的处理。将本次请求的request、session、flash等放在了这个RequestContext对象ctx中。

ctx.push是RequestContext的push方法,它内部会执行_request_ctx_stack.push(self),self是ctx本身,_request_ctx_stack是LocalStack对象(程序启动后就产生的一个单例对象),它执行了push并传入了ctx(这个ctx是本次请求特有的)

## LocalStack
def push(self, obj):
    # self._local是 Flask自己定义的兼容线程和协程的LocalProxy对象
    # self._local中反射 stack,会根据不同线程或协程,返回不同线程的stack
    #rv是None的
    rv = getattr(self._local, "stack", None)
    if rv is None:
        # rv=[]
        # self._local.stack=rv
        #self._local={'协程id号1':{stack:[]},'协程id号2':{stack:[]}}
        self._local.stack = rv = []
        rv.append(obj)
        #self._local={'协程id号1':{stack:[ctx,]},'协程id号2':{stack:[]}}
        return rv

self._local引用了全局变量代理local,程序启动时就产生了,在local中有塞入stack,会根据线程或者协程号将obj(传入的ctx)存放起来。

Local与LocalProxy

我们在处理多线程数据时,需要注意同一进程下的多个线程的全局变量是共享的。我们在处理数据通常采取加锁处理,来避免数据错乱。

不过有这么一个应用场景,每个线程的任务想要使用的变量来自于这个线程其他任务函数,有时这些函数是跨文件的,我们需要把一些数据导入到全局使用。那我们又想多个线程间的数据不错乱,就需要用到threading.local对象,如果上面这段话有点拗口难懂,看下面的代码即可:

from threding import Thread, Local

g = Local()

def task(arg):
    g.value = arg
    time.sleep(1)
    print g.value
    
for i in range(10):
    t = Thread(target=task, args=(i, ))
    t.start()
    
"""执行结果,是打印出了0~9的数字而不是全是一个值"""

上面程序的执行结果说明了,每个线程虽然都是在1秒前左右赋值全局的g.value,在1秒后再打印全局g.value,但是在打印时,打印的都是自己线程中所赋值的数据,并没有出现数据错乱。

那么这个g对象,我在一个文件中使用Local产生,导入到一个文件中进行赋值,再导入另一个文件中进行取值,这样的操作也就可以实现了,这就符合我们对上下文操作的要求了。

实际上,flask中的全局request,session,g也就是这样的原理。

但是flask自己写了一个LocalProxy类,因为原生的Local是根据线程号来区分不同任务的数据的,但flask还可以开启协程去处理任务,所以为了兼容协程,flask自己封装了一个LocalProxy,即使任务是不同协程开启的,这个全局变量LocalProxy对象也能区分开。

  • Local是内置模块threading提供的全局变量代理,由线程号区分任务的数据
  • LocalProxy是flask封装的全局变量代理,由协程号(如果有协程模块的话)区分任务的数据

了解了以上内容后,再回到self._local.stack=rv=[]rv.append(obj),再理解就方便了,就是将我们之前处理好的ctx内容存入了local.stack中,并且按照线程号/协程号可以找到它。

至此,从run执行到视图之前,我们关于request,session,g等全局变量的存储处理就有了个大概的认识,它们都被打包到了ctx的上下文变量中,并放入了local全局变量代理中,下文则要说明取出这些数据使用的源码是如何的对应的

request.method

  1. request = LocalProxy(partial(_lookup_req_object, "request"))

    request实际上是通过了以上代码先产生了一个全局变量代理对象。其中partial(_lookup_req_object, "request")是偏函数,我们先传入了参数,当后面再加括号调用时才真正执行。

  2. request取属性会触发LocalProxy的__getattr__,传入name比如这里的'method',返回getattr(self._get_current_object(), name),也就是由self._get_current_object()反射出的method

  3. _get_current_object()

    def _get_current_object(self):
        if not hasattr(self.__local, "__release_local__"):
            return self.__local() # self中的 __local,隐藏属性
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)
    

    __local是LocalProxy的隐藏属性,在__init__中赋的值

  4. __init__

    def __init__(self, local, ...):
    	object.__setattr__(self, "_LocalProxy__local", local)
    

    只看这一部分我们即可得知,local是生成对象时传入的,它就是request = LocalProxy(partial(_lookup_req_object, "request"))中的partial(_lookup_req_object, "request"),所以在第三步_get_current_object()中我们通过self.__local()去执行了_lookup_req_object('request')函数,并作为返回值

  5. _lookup_req_object('request')

    def _lookup_req_object(name):
            # 这里把当前线程下 的ctx取出来了
            top = _request_ctx_stack.top
            if top is None:
                raise RuntimeError(_request_ctx_err_msg)
            return getattr(top, name) # 去ctx中反射request,返回的就是当次请求的request
    

    所以这个当次请求的request就是第三步_get_current_object()的结果,第二步request.method触发的__getattr__返回的值就是getattr(当次请求的request,'method')

整理完上述取值过程,对于所有request,session,g等本地代理的取值实际上是类似的,都是先传了个偏函数作为self.__local,在_get_current_object()判断如果是偏函数就加括号调用,那么调用后就得到了当前线程的相应的对象,我们在调用有关于这个函数有关属性和方法时都经过代理的__getattrr__反射出了原本对象该有的属性。

标签:__,flask,ctx,self,request,源码,environ,解析,local
From: https://www.cnblogs.com/Leethon-lizhilog/p/17297188.html

相关文章

  • flask5
    今日内容1信号#Flask框架中的信号基于blinker(安装这个模块),其主要就是让开发者可是在flask请求过程中定制一些用户行为flask和django都有#观察者模式,又叫发布-订阅(Publish/Subscribe)23种设计模式之一pip3.8installblinker#信号:signial翻译过来的,并发编程中学过......
  • 找人写指标 分时预警牛皇预警公式源码
    分时抓涨停预警源码 抓涨停不难分时预警牛皇520预警源码公布源码{牛皇}{副图}{大智慧L2公式}原理解析:A1赋值:((如果(((收盘价/((成交额(元)的历史累和)/((成交量(手)*100)的历史累和)))位于1.05和0.95之间)=0),返回(收盘价的历史简单移动平均),否则返回((成交额(元)的历史累和)/(......
  • flask框架04 导出项目 local flask生命执行流程 wtforms
    今日内容详细目录今日内容详细1请求上下文分析(源码:request原理)1.1导出项目的依赖1.2函数和方法1.3threading.local对象1.4偏函数1.5flask整个生命执行流程(1.1.4版本为例)2wtforms(了解)1请求上下文分析(源码:request原理)1.1导出项目的依赖#之前pipfreeze>requ......
  • 源码共读 | tdesign-vue 初始化组件
    前言Tdesign-vue是一由腾讯开源的Vue.js组件库。我们知道,这些大型的组件库业务覆盖面很广,基本都包含了很多组件,很多组件包含了一些通用性代码,如果每开发一个组件,都去把这些通用性代码复制出来,无疑是非常繁琐的,那么作者在开发这些组件时是如何做的呢?学习目标:新增组件:npmrun......
  • [博客入坑]CS231N assignment 1 _ KNN 知识 & 详细解析
    从零开始的全新博客我之前一直在语雀上更新文章,但是一会不更发现居然需要VIP才能发博客了:不过考虑到自己确实有一会没写博客了,之前对神经网络在课上学过,也鼓捣过pytorch,但是深感自己没有系统学习过.第一次接触这种公开课,希望也能有种从零开始的感觉,让自己面对这......
  • 分布式存储技术(下):宽表存储与全文搜索引擎的架构原理、特性、优缺点解析
    对于写密集型应用,每天写入量巨大,数据增长量无法预估,且对性能和可靠性要求非常高,普通关系型数据库无法满足其需求。对于全文搜索和数据分析这类对查询性能要求极高的场景也是如此。为了进一步满足上面两类场景的需求,有了宽表存储和搜索引擎技术,本文将对他们的架构、原理、优缺点做介......
  • 分布式存储技术(下):宽表存储与全文搜索引擎的架构原理、特性、优缺点解析
    对于写密集型应用,每天写入量巨大,数据增长量无法预估,且对性能和可靠性要求非常高,普通关系型数据库无法满足其需求。对于全文搜索和数据分析这类对查询性能要求极高的场景也是如此。为了进一步满足上面两类场景的需求,有了宽表存储和搜索引擎技术,本文将对他们的架构、原理、优缺点做介......
  • Go-context源码解析
    首先我们简单的来看一个例子,如下:(学好这个例子,我们就可以说完全掌握住context了,并且能重构一个contextfuncmain(){ ctx,cancel:=context.WithCancel(context.Background()) ctxV:=context.WithValue(ctx,1,"HelloWorld") gofunc(ctxcontext.Context){ val:=......
  • SDL_AudioSpec 解析以及使用说明
    前言SDL_AudioSpec是包含音频输出格式的结构体,同时它也包含当音频设备需要更多数据时调用的回调函数。解析头文件说明typedefstructSDL_AudioSpec{intfreq;/**<DSPfrequency--samplespersecond*/SDL_AudioFormatformat;/**<Audiod......
  • Python源码笔记——Python中的列表对象
    1.列表结构体#definePyObject_VAR_HEADPyVarObjectob_base;typedefstruct{PyObjectob_base;Py_ssize_tob_size;/*Numberofitemsinvariablepart*/}PyVarObject;typedefstruct{PyObject_VAR_HEAD/*Vectorofpointerstolistel......