flask框架之上下文详解、请求上下文、应用上下文、Local代理、g对象、

前言

这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题

于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。

微信小程序搜索:Python面试宝典

或可关注原创个人博客:https://lienze.tech

也可关注微信公众号,不定时发送各类有趣猎奇的技术文章:Python编程学习

上下文

即语境,语意,在程序中可以理解为在代码执行到某个时刻,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以做的事情

Flask中有两种上下文:请求上下文(request context)应用上下文(application context)

Flask中上下文对象:相当于一个容器,保存了Flask程序运行过程中的一些信息

  1. application指的是当你调用app = flask(name)创建的这个对象app
  2. request指的是每次http请求发生时,WSGI server(比如gunicorn)调用Flask.call()之后,在Flask对象内部创建的Request对象
  3. application表示响应请求的应用本身request表示响应的http请求
  4. appliacation的生命周期大于request,一个application存活期间,可能发生多次http请求,所以,也就会有多个request

request == 一次http请求

appliacation == 当前请求应用


Request context

在Flask 0.9版本之前,应用只有请求上下文对象,包含了和请求处理相关的信息

在Flask中,可以直接在视图函数中使用request这个对象进行获取相关数据,而request就是请求上下文的对象,保存了当前本次请求的相关数据

请求上下文对象有:requestsession

  • request: 封装了HTTP请求的内容,针对的是http请求

  • session: 用来记录请求会话中的信息,针对的是用户信息

整个请求的处理过程,都会在这个上下文对象中进行。这保证了请求的处理过程不被干扰

App Context

应用上下文经常用来存储的是一些有关用户和配置信息,不是请求相关的

应用上下文有:current_app,g

current_app

应用程序上下文,用于存储应用程序中的变量

也可以在current_app中存储一些变量

  • 应用的启动脚本是哪个文件,启动时指定了哪些参数
  • 加载了哪些配置文件,导入了哪些配置
  • 连接了哪个数据库
  • 有哪些可以调用的工具类、常量
  • 当前flask应用在哪个机器上,哪个IP上运行,内存多大

g

g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,在每个视图中都是不同的

g对象在一次请求中的所有的代码的地方,都是可以使用的

from flask import g
  • g有啥用?

举个例子,你可以在before_request里面做Http Basic Authentication验证,然后将验证过的用户数据存在g里面,这样在视图里就可以直接调用g里面的用户数据了,而不用再搞个全局变量,这样非常方便

生命周期

  • current_app 的生命周期最长,只要当前程序实例还在运行,都不会失效。
  • Requestg 的生命周期为一次请求期间,当请求处理完成后,生命周期也就完结了
  • Session 就是传统意义上的 session 。只要它还未失效(用户未关闭浏览器、没有超过设定的失效时间),那么不同的请求会共用同样的 session

区别

  • 请求上下文:保存了客户端和服务器交互的数据
  • 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等

Local

思考:在flask项目中某一个功能中会有多个视图,怎么保证某次请求的上下文不会被别的视图拿走呢?

  • 啥是local?

很简单,用来隔离线程之间数据的,类似一个字典,众所周知,线程的数据都是共享的,那么我现在就是想有一些线程内的私有数据该咋办?

local = {
    
    thread_id: {
    
    上下文数据key: 上下文数据value }}

那就使用如下在Python的标准库中提供几个对象来完成某个线程内的全局变量的设置

thread local用于存储thread-safethread-specific的数据,通过这种方式存储的数据只在本线程中有效,而对于其它线程则不可见。正是基于这样的特性,我们可以把针对线程全局的数据存储进thread local对象

from threading import local # 线程模块内导包
thread_local_data = local() # 初始化local对象
thread_local_data.name="ZegeNB" # 存储一个变量
thread_local_data.name # 输出看看
'ZegeNB'

使用thread local对象虽然可以基于线程存储全局变量,但是在Web应用中可能会存在如下问题:

  1. 有些应用使用的是greenlet协程,这种情况下无法保证协程之间数据的隔离,因为不同的协程可以在同一个线程当中
  2. 即使使用的是线程,WSGI应用也无法保证每个http请求使用的都是不同的线程,因为后一个http请求可能使用的是之前的http请求的线程,这样的话存储于thread local中的数据可能是之前残留的数据,这就好比你穿了别人穿过的衣服,啧啧

为了解决上述问题,Werkzeug开发了自己的local对象

from werkzeug.local import Local, LocalManager

Werkzeug的local对象重写了关于上下文存储对象方法

主要是通用在greenlet或者线程中使用local对象时,自动获取对应的greenlet id(或者线程id),从而获取到对应的dict存储空间,再通过name key就可以获取到真正的存储的对象,这样有效区分开了在线程或是协程中

这个技巧实际上在编写线程安全或协程安全的代码时是非常有用的,即通过线程id(或协程id)来分别存储数据

LocalStack

LocalStack与Local对象类似,都是可以基于Greenlet协程或者线程进行全局存储的存储空间,实际LocalStack就是对Local进行了二次封装,区别在于其数据结构是栈的形式

LocalStack有两个特性:

  1. 作为栈的特性,先进后出
  2. 作为线程隔离对象的特性

LocalStack是基于栈,所以可以通过push和pop来存储和弹出数据

>>> ls = LocalStack()
>>> ls.push(42)
>>> ls.top
42
>>> ls.push(23)
>>> ls.top
23
>>> ls.pop()
23

LocalStack在Flask框架中会频繁的出现,其Request ContextApp Context的实现都是基于LocalStack

flask提供了两个LoclStack,分别存储Request context、App Context


那么问题就来了,直接使用Local也可以实现的功能,为啥非要搞成个栈?

通过 LocalStack 实现栈结构而不直接使用 Local 的目的是为了在多应用情景下让一个请求可以很简单的知道当前上下文是哪个

每个请求,其相关的上下文就在栈顶,直接将栈顶**上下文**出栈就可以获得当前请求对应上下文中的信息了

LocalProxy

LocalProxy 类是 Local 类的代理对象,它的作用就是将操作都转发到 Local 对象上

@implements_bool
class LocalProxy(object):
	...

总结: 所谓 Flask 上下文,其实就是基于 list 实现的栈,这个 list 存放在 Local 类实例化的对象中,Local 类利用线程 id 作为字典的 key,线程具体的值作为字典的 values 来实现线程安全,使用的过程就是出栈入栈的过程,此外,在具体使用时,会通过 LocalProxy 类将操作都代理给 Local 类对象。

猜你喜欢

转载自blog.csdn.net/HeroicLee/article/details/120991462