在上一节中,我跟大家一起深入了解了一下Python的「上下文管理器 」。而今天呢,还是和上下文有关的话题。只不过这里的上下文和上一节的内容有点不一样,上下文管理器是管理代码块级别的上下文,而今天要讲的上下文是工程项目中的上下文。

可能你现在对上下文这个概念,还是不太清楚。这里再简单说明一下

一段程序或函数的运行,很多情况下,是需要依赖程序外的变量才能够运行的,一旦脱离了这些变量,程序就没法正常工作。这些外部变量,按照常规的做法,是将这些变量做为函数的参数,一个一个地传入进去。这是一种做法,对于简单的小程序完全没有问题,一旦在较大的工程项目中,还采用这样的做法,就显得过于笨重,不灵活了。

一个较好的做法是,将这些项目全局中需要频繁用到的变量值整合在一起,而这些值的集合就是上下文。只要在需要的时候,从这个上下文中取就好了。

在 Flask 中,有两种上下文

  • application context
  • request context

application context 会存储一个 app 里可以全局共享里的变量。而 request context 会存储一个从外部发起的请求的所有信息。

一个 Flask 项目里,可以有多个 app, 而在一个 app 里会有多个 request。这是它们之间的对应关系。

上面说了,上下文可以实现 信息共享,但同时有一点很重要,就是 信息隔离 。在多个 app 同时运行的时候,要能保证一个 app 不能访问和改变到另一个 app 的变量。这个很重要。

那么具体是如何做到 信息隔离 的呢?

接下来,就要提到在 Flask 中三个很常见的对象,你应该不会感到陌生。

  • Local
  • LocalProxy
  • LocalStack

这三个对象都是 werkzeug 里提供的,定义的 local.py 里,所以它们并不是Flask 中特有的, 这就意味着我们可以直接在自己的项目中使用它们,而不用依托于 Flask 的环境。

1. Local

首先是 Local ,记得在以前的「并发编程系列」的第五篇线程的 信息隔离 的时候,提过了 threading.local ,它是专门用来存储当前线程的变量,从而实现对象的线程隔离。

而 Flask 里的 Local 和这它的是一个作用。

为了搞懂这个 Local 的作用,最好的方式就是直接对比两段代码。

首先是,不使用 Local 的情况,我们新建一个类,里面有name属性,默认为 wangbm,然后开始一个线程,做的事就是将这个 name 属性改为 wuyanzu。

import time
import threadingclass People:name = 'wangbm'my_obj = People()def worker():my_obj.name = 'wuyanzu'new_task = threading.Thread(target=worker)
new_task.start()# 休眠1s,保证子线程先执行
time.sleep(1)print(my_obj.name)

执行结果可想而知,是 wuyanzu 。子线程 对对象的更改可以直接影响到主线程。

接下来,我们使用 Local 来实现一下

import time
import threadingfrom werkzeug.local import Localclass People:name = 'wangbm'my_obj = Local()
my_obj.name = 'wangbm'def worker():my_obj.name = 'wuyanzu'print('in subprocess, my_obj.name: '+str(my_obj.name))new_task = threading.Thread(target=worker)
new_task.start()# 休眠1s,保证子线程先执行
time.sleep(1)print('in mainprocess, my_obj.name: '+str(my_obj.name))

打印结果如下,可见子线程的修改并不会影响主线程

in subprocess, my_obj.name: wuyanzu
in mainprocess, my_obj.name: wangbm

那么 Local 是如何做到的呢,其实原理很简单,就是利用了基本的数据结构:字典。

当线程去修改 Local 对象里的变量(包含变量名 k1 和变量值 v1 )时,通过源码可知,他是先获取当前线程的id,作为__storage__ (这个storage是个嵌套字典)的key,而value 呢,就是一个字典,{k1: v1}

    def __setattr__(self, name, value):ident = self.__ident_func__()storage = self.__storage__try:storage[ident][name] = valueexcept KeyError:storage[ident] = {name: value}

举例如下

# 0 和 1 是线程 id
self.__storage__['0'][k1] = v1
self.__storage__['1'][k2] = v2

正时因为用了线程id 作了一层封装,才得以实现了线程隔离。

如果要用图来表示,最开始的Local对象就是一个空盒子

当有不同的线程往里写数据时,Local 对象为每个线程分配了一个 micro-box。

local 是需要被 localmanager 管理的,在请求结束后,会调用 localmanager.cleanup() 函数,其实是调用 local.__release_local__ 进行数据清理。是如何做到的呢,看下面这段代码。

   from werkzeug.local import Local, LocalManagerlocal = Local()local_manager = LocalManager([local])def application(environ, start_response):local.request = request = Request(environ)...# make_middleware会确保当request结束时,所有存储于local中的对象的reference被清除application = local_manager.make_middleware(application)

以下就是 Local 的代码,有需要的可以直接看这里。

class Local(object):__slots__ = ('__storage__', '__ident_func__')def __init__(self):object.__setattr__(self, '__storage__', {})object.__setattr__(self, '__ident_func__', get_ident)def __iter__(self):return iter(self.__storage__.items())def __call__(self, proxy):"""Create a proxy for a name."""return LocalProxy(self, proxy)def __release_local__(self):self.__storage__.pop(self.__ident_func__(), None)def __getattr__(self, name):try:return self.__storage__[self.__ident_func__()][name]except KeyError:raise AttributeError(name)def __setattr__(self, name, value):ident = self.__ident_func__()storage = self.__storage__try:storage[ident][name] = valueexcept KeyError:storage[ident] = {name: value}def __delattr__(self, name):try:del self.__storage__[self.__ident_func__()][name]except KeyError:raise AttributeError(name)

2. LocalStack

通过对 Local 的介绍,可以知道 Local 其实是通过封装了字典的,以此实现了线程隔离。

而接下来要介绍的 LocalStack ,也是同样的思想,LocalStack 是封装了 Local ,所以它既有了 Local 的线程隔离的特性,也有了栈结构的特性,可以通过 pop,push,top 来访问对象。

同样用一张图来表示

栈结构的特性,无非就是后进先出。这里就不说了,这里的重点是线程隔离的特性如何体现,还是以上面的例子,稍微做了下修改。

import time
import threadingfrom werkzeug.local import LocalStackmy_stack = LocalStack()
my_stack.push('wangbm')def worker():print('in subthread, my_stack.top is : '+str(my_stack.top) + ' before push')my_stack.push('wuyanzu')print('in subthread, my_stack.top is : ' + str(my_stack.top) + ' after push')new_task = threading.Thread(target=worker)
new_task.start()# 休眠1s,保证子线程先执行
time.sleep(1)print('in main thread, my_stack.top is : '+str(my_stack.top))

输出的结果如下,可见子线程的里的 my_stack 和主线程里的 my_stack 并不能共享,确实实现了隔离。

in subthread, my_stack.top is : None before push
in subthread, my_stack.top is : wuyanzu after push
in main thread, my_stack.top is : wangbm

在 Flask 中,主要有两种上下文,AppContextRequestContext

当一个请求发起后,Flask 会先开启一个线程,然后将包含请求信息的上下文 RequestContext 推入一个 LocalStack 对象中(_request_ctx_stack),而在推入之前,其实它会去检测另一个 LocalStack 对象(_app_ctx_stack)是否为空(但是一般 _app_ctx_stack 都不会为空),如果为空就先将app的上下文信息push到_app_ctx_stack,然后再去把请求的上下文信息push到_request_ctx_stack 里。

在flask中有三个对象比较常用

  • current_app
  • request
  • session

这三个对象,永远是指向LocalStack 栈顶的上下文中对应的app、request或者session,对应的源码如下:

def _lookup_req_object(name):top = _request_ctx_stack.topif top is None:raise RuntimeError(_request_ctx_err_msg)return getattr(top, name)def _find_app():top = _app_ctx_stack.topif top is None:raise RuntimeError(_app_ctx_err_msg)return top.app_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

3. LocalProxy

通过上面的代码,你可以发现,我们访问LocalStack里的元素的时候,都是通过LocalProxy 来进行的有没有?

这就很奇怪了,为什么不直接访问LocalLocalStack呢?

这应该是个难点,我这边举个例子,也许你就明白了。

首先是不使用LocalProxy的情况

# use Local object directly
from werkzeug.local import LocalStackuser_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})def get_user():# do something to get User object and return itreturn user_stack.pop()# 直接调用函数获取user对象
user = get_user()
print user['name']
print user['name']

输出结果是

John
John

使用LocalProxy后

# use LocalProxy
from werkzeug.local import LocalStack, LocalProxy
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})def get_user():# do something to get User object and return itreturn user_stack.pop()# 通过LocalProxy使用user对象
user = LocalProxy(get_user)
print user['name']
print user['name']

输出结果

John
Bob

怎么样,看出区别了吧,直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里[]操作符用于获取属性),都会重新获取user, 从而实现了动态更新user的效果

每次 user['name'] 的时候 就会触发 LocalProxy 类的 __getitem__,从而调用该类的 _get_current_object。而每次 _get_current_object都会返回 get_user()(在flask中对应的函数是 _lookup_req_object ) 的执行结果, 也就是 user_stack.pop()

def __init__(self, local, name=None):# 【重要】将local对象(也就是一个get_user函数对象)赋值给self.__localobject.__setattr__(self, '_LocalProxy__local', local)object.__setattr__(self, '__name__', name)if callable(local) and not hasattr(local, '__release_local__'):# "local" is a callable that is not an instance of Local or# LocalManager: mark it as a wrapped function.object.__setattr__(self, '__wrapped__', local)def _get_current_object(self):"""Return the current object.  This is useful if you want the realobject behind the proxy at a time for performance reasons or becauseyou want to pass the object into a different context."""if not hasattr(self.__local, '__release_local__'):# 【重要】执行传递进行的 get_user 对象。return self.__local()try:return getattr(self.__local, self.__name__)except AttributeError:raise RuntimeError('no object bound to %s' % self.__name__)

这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。

4. 经典错误

在 Flask 中经常会遇到的一个错误是:

Working outside of application context.

这个错误,如果没有理解 flask 的上下文机制,是很难理解的。通过上面知识背景的铺垫,我们可以尝试来搞懂一下为什么会出现这样的情况。

首先我们先来模拟一下这个错误的产生。假设现在有一个单独的文件,内容如下

from flask import current_appapp = Flask(__name__)app = current_app
print(app.config['DEBUG'])

运行一下,会报如下错误。

Traceback (most recent call last):File "/Users/MING/PycharmProjects/fisher/app/mytest/mytest.py", line 19, in <module>print(app.config['DEBUG'])File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__return getattr(self._get_current_object(), name)File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_objectreturn self.__local()File "/Users/MING/.virtualenvs/fisher-gSdA58aK/lib/python3.6/site-packages/flask/globals.py", line 51, in _find_appraise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.

你一定会奇怪吧。我明明也实例化一个app对象,但是为什么取current_app会报错呢?而如果不用current_app,就不会报错。

如果你认真学习了上面的内容,这边也就不难理解了。

从先前的研究发现,当使用current_app时,它取的是LocalStack的栈顶元素(app的上下文信息),而实际上在我们通过app = Flask(__name__)实例化一个app对象时,此时还没有将这个上下文信息写入LocalStack,自然取栈顶元素就会出错了。

def _find_app():top = _app_ctx_stack.topif top is None:raise RuntimeError(_app_ctx_err_msg)return top.app

上面我们也说过了,这个上下文什么时候push进去呢?在外部发起一起request请求后,首先就会先检查 app 的上下文信息是否已经 push 进去了,如果没有的话,就会先半其push进去。

而上面我们是以运行单个文件的方式,并没有实际产生一个 request 请求,自然 在 LocalStack 里没有 app的上下文信息。报错也是正常的。

知道了错误根源后,如何解决这种问题呢?

在Flask中,它提供了一个方法ctx=app.app_context()可以获取一个上下文对象,我们只要将这个上下文对象 手动 push 到 LocalStack 中,current_app 也就可以正常取到我们的app对象了。

from flask import Flask, current_appapp = Flask(__name__)
ctx = app.app_context()
ctx.push()app = current_app
print(app.config['DEBUG'])
ctx.pop()

由于 AppContext 类实现了上下文协议

class AppContext(object):def __enter__(self):self.push()return selfdef __exit__(self, exc_type, exc_value, tb):self.pop(exc_value)if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:reraise(exc_type, exc_value, tb)

所以你也可以这样写

from flask import Flask, current_appapp = Flask(__name__)with app.app_context():app = current_appprint(app.config['DEBUG'])

以上,是我通过学习七月的 Flask高级编程 加上自己直白的理解,希望对你在理解 Flask的上下文核心机制 会有帮助。

flask项目源码_源码解读:Flask上下文与代理模式相关推荐

  1. 源码_网站源码_游戏源码_源码下载-开源之家

    开源之家 - 建站6年,站内有海量网站源码(asp源码,php源码,.net源码),游戏源码(VC++源码,C#源码,C++源码),商业源码,网站模板,微信源码,区块链源码,网游源码提供给大家下载. ...

  2. python手工打码_打码兔和超人打码python版

    1.[代码][Python]代码 # coding:utf-8 from ctypes import * import requests import json import random impor ...

  3. python的flask框架显示柱状图_使用Python的Flask框架,结合Highchart,动态渲染图表...

    服务端动态渲染图表 参考文章链接:https://www.highcharts.com.cn/docs/dynamic-produce-html-page 参考文章是使用php写的,我这边改用pyth ...

  4. python的flask框架显示柱状图_使用Python的Flask框架,结合Highchart,动态渲染图表(Ajax 请求数据接口)...

    参考链接:https://www.highcharts.com.cn/docs/ajax 参考链接中的示例代码是使用php写的,这里改用python写. 需要注意的地方: 1.接口返回的数据格式,这个 ...

  5. 前端设定项目奖金有多少_【系列二】建筑工程项目激励模式探讨

    ​​建筑工程一般以项目制的形式运作,如何在这种"一次性"的组织形式下,确保项目符合质量.安全.进度等方面的要求,并确保项目的经济效益,对项目人员实施恰当的项目激励是关键. 我们认为 ...

  6. 扫码点餐小程序源码_扫码点餐小程序有什么用?怎么制作?

    现在小程序扫码点餐服务已经越来越普及,当用户需要点餐时,无需麻烦服务人员,只需扫描餐桌上或者海报上的小程序码,就能快速点餐下单.这样不仅节约了排队时间,也提高了商家自己的服务效率. 上线了小程序案例, ...

  7. php-mcrypt 源码_源码方式安装php扩展mcrypt

    本文实际上是在CentOS下进行的,原理和在Ubuntu下源码安装一样,下图首先示例mcrypt和php的依赖关系 基本原理是:首先使mcrypt软件能够运行,然后安装php扩展模块,并在php.in ...

  8. mysql php apache源码_源码安装apache+mysql+php

    源码安装apache+mysql+php #!/bin/sh #byliangz at 2010-08-14 #环境: #     1. CentOS5.5或RHEL5.4,配置好IP地址,主机名等信 ...

  9. mysql查询优化器源码_源码下载网浅析MySQL 查询优化器

    源码下载网浅析MySQL 查询优化器 时间:2019-01-18 17:45作者:网友投稿 优化器(The Optimizer) 这篇描述MySQL查询优化器的工作原理.MySQL查询优化器主要为执行 ...

最新文章

  1. HiveServer2
  2. vue element upload 控件用form-data上传方式导入xls文件
  3. ssm read time out的原因_有高血压的人,认清这4点,很多高血压一直降不下来,原因在这里...
  4. Keil5报错:error: more than one instance overload function “xx“ has C linkage
  5. linux - python - 异常:error while loading shared libraries
  6. java连接linux服务器执行shell命令(框架分析+推荐)
  7. github搭建个人博客 hexo d无效
  8. matlab矩阵的低秩分解,低秩分解的matlab代码看不懂,分解的两个矩阵在哪呀??...
  9. RAC srvctl 命令报 libpthread.so.0 cannot open shared object file No such file or directory 解决方法...
  10. 如果你是中国普通式的IT人——总有些无关风月的执着
  11. Windows系统查询硬盘序列号
  12. 基于stm32智能门锁系统
  13. 《高效能人士的七个习惯》:运用才是关键
  14. 【题解】LuoGu5369:[PKUSC2018]最大前缀和
  15. 【Verilog】加法器减法器的设计
  16. Linux入侵之隐藏你的踪迹
  17. Oracle语法求水仙花数,python实现水仙花数实例讲解
  18. Java上路09-多线程
  19. Python安全小工具之Web目录扫描器
  20. 小孙讲认证:ASTM F963 -17 CPSIA CPC 适用产品:婴儿磨牙产品、牙胶

热门文章

  1. 防止自建控件与页面间重复引入客户端js脚本的方法
  2. 分形与数据结构第一篇(神奇的色子)
  3. ios assign、copy 、retain
  4. linux命令行模式连接网络,在Linux环境命令行中实现Wifi 连接的方法
  5. 7月21日王者服务器维修,2020年7月21日王者荣耀世冠杯,GOG2:0EMC,实力碾压运营滴水不漏...
  6. mysql表 spid program_oracle 解锁某张表 和编译存储过程卡死问题处理
  7. log功能ASP.NET MVC能实现
  8. 30个超实用Python代码片段
  9. 2. 抓ARP包, 抓PPPoE包
  10. Portal: 强叔侃墙