关于我

一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。

Github:https://github.com/hylinux1024

微信公众号:终身开发者(angrycode)

Flask中全局变量有current_apprequestgsession。不过需要注意的是虽然标题是写着全局变量,但实际上这些变量都跟当前请求的上下文环境有关,下面一起来看看。

current_app是当前激活程序的应用实例;request是请求对象,封装了客户端发出的HTTP请求中的内容;g是处理请求时用作临时存储的对象,每次请求都会重设这个变量;session是用户会话,用于存储请求之间需要保存的值,它是一个字典。

0x00 current_app

应用程序上下文可用于跟踪一个请求过程中的应用程序实例。可以像使用全局变量一样直接导入就可以使用 (注意这个变量并不是全局变量)

Flask实例有许多属性,例如config可以Flask进行配置。

一般在创建Flask实例时

from flask import Flask
app = Flask(__name__)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
...

通常不会直接导入app这个变量,而是使用通过导入current_app这个应用上下文实例代理

from flask import current_app
current_app 的生命周期

Flask应用在处理客户端请求(request)时,会在当前处理请求的线程中推送(push)一个上下文实例和请求实例(request),请求结束时就会弹出(pop)请求实例和上下文实例,所以current_apprequest是具有相同的生命周期的,且是绑定在当前处理请求的线程上的。

如果一个没有推送上下文实例就直接使用current_app,会报错

RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that
needed to interface with the current application object in some way.
To solve this, set up an application context with app.app_context().

如果要直接使用current_app就要手动推送(push)应用上下文实例,从上面的错误信息可以知道,可以使用with语句,帮助我们push一个上下文实例

def create_app():
app = Flask(__name__) with app.app_context():
init_db() return app

需要注意的是current_app是“线程”本地变量,所以current_app需要在视图函数或命令行函数中使用,否则也会报错。

要理解这一点就要对服务器程序工作机制有所了解。一般服务器程序都是多线程程序,它会维护一个线程池,对于每个请求,服务器会从线程池中获取一个线程用于处理这个客户端的请求,而应用的current_apprequest等变量是“线程”本地变量,它们是绑定在“线程”中的(相当于线程自己独立的内存空间),所以也在线程环境下才能够使用。

Flask中是否也是通过线程本地变量来实现的呢?这个问题我们在后面的工作原理一节会给出答案。

0x01 g

若要在应用上下文中存储数据,Flask提供了g这个变量为我们达到这个目的。g其实就是global的缩写,它的生命周期是跟应用上下文的生命周期是一样的。

例如在一次请求中会多次查询数据库,可以把这个数据库连接实例保存在当次请求的g变量中,在应用上下文生命周期结束关闭连接。

from flask import g

def get_db():
if 'db' not in g:
g.db = connect_to_database() return g.db @app.teardown_appcontext
def teardown_db():
db = g.pop('db', None) if db is not None:
db.close()

0x02 request

request封装了客户端的HTTP请求,它也是一个线程本地变量。

没有把这个变量放在处理api请求的函数中,而是通过线程本地变量进行封装,极大地方便使用,以及也使得代码更加简洁。

request的生命周期是跟current_app是一样的,从请求开始时创建到请求结束销毁。同样地Flask在处理请求时就会push 一个request和应用上下文的代理实例,然后才可以使用。如果没有push就使用就会报错

RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.

通常这个错误在测试代码中会经常遇到,如果需要在单元测试中使用request,可以使用test_client或者在with语句中使用test_requet_context()进行模拟

def generate_report(year):
format = request.args.get('format')
... with app.test_request_context(
'/make_report/2017', data={'format': 'short'}):
generate_report()

0x03 session

前面讲到如果在一个请求期间共享数据,可以使用g变量,但如果要在不同的请求(request)之间共享数据,那就需要使用session,这是一个私有存储的字典类型。可以像操作字典一样操作session

session是用户会话,可以保存请求之间的数据。例如在使用login接口进行用户登录之后,把用户登录信息保存在session中,然后访问其它接口时就可以通过session获取到用户的登录信息。


@app.route('/login')
def login():
# 省略登录操作
...
session['user_id']=userinfo @app.route('/show')
def showuser():
# 省略其它操作
...
userid = request.args.get('user_id')
userinfo = session.get(userid)

0x04 工作原理

我们知道Flask在处理一个请求时,wsgi_app()这个方法会被执行。而在Flask的源码内部requestcurrent_app是通过_request_ctx_stack这个栈结构来保存的,分别为

# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)

需要注意最新的版本源码会有些不同requestcurrent_app分别是有两个栈结构来存储:_request_ctx_stack_app_ctx_stack。但新旧代码思路是差不多的。

最新的源码里,全局变量的定义

# context locals
_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"))
g = LocalProxy(partial(_lookup_app_object, "g"))

其中_find_app_lookup_app_object方法是这样定义的

def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name) def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)

可以看到current_appgLocalProxy通过_app_ctx_stack.top进行封装的。requestsession_request_ctx_stack的封装。LocalProxywerkzeug库中local对象的代理。LocalStack顾名思义是一个实现了栈的数据结构。

前面提到全局变量是跟线程绑定的,每个线程都有一个独立的内存空间,在A线程设置的变量,在B线程是无法获取的,只有在A线程中才能获取到这个变量。这个在Python的标准库有thread locals的概念。

然而在Python中除了线程外还有进程和协程可以处理并发程序的技术。所以为了解决这个问题Flask的依赖库werkzeug就实现了自己的本地变量werkzeug.local。它的工作机制跟线程本地变量(thread locals)是类似的。

要使用werkzug.local

from werkzeug.local import Local, LocalManager

local = Local()
local_manager = LocalManager([local]) def application(environ, start_response):
local.request = request = Request(environ)
... application = local_manager.make_middleware(application)

application(environ,start_response)方法中就把封装了请求信息的request变量绑定到了local变量中。然后在相同的上下文下例如在一次请求期间,就可以通过local.request来获取到这个请求对应的request信息。

同时还可以看到LocalManager这个类,它是本地变量管理器,它可以确保在请求结束之后及时的清理本地变量信息。

在源码中对LocalManager是这样注释的

Local objects cannot manage themselves. For that you need a local

manager. You can pass a local manager multiple locals or add them later

by appending them to manager.locals. Every time the manager cleans up,

it will clean up all the data left in the locals for this context.

Local不能自我管理,需要借助LocalManager这个管家来实现请求结束后的清理工作。

0x05 总结

current_appgrequestsessionFlask中常见4个全局变量。current_app是当前Flask服务运行的实例,g用于在应用上下文期间保存数据的变量,request封装了客户端的请求信息,session代表了用户会话信息。

0x06 学习资料

Python Web Flask源码解读(四)——全局变量的更多相关文章

  1. Python Web Flask源码解读(一)——启动流程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. Python Web Flask源码解读(二)——路由原理

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  3. Python Web Flask源码解读(三)——模板渲染过程

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  4. Bert系列 源码解读 四 篇章

    Bert系列(一)——demo运行 Bert系列(二)——模型主体源码解读 Bert系列(三)——源码解读之Pre-trainBert系列(四)——源码解读之Fine-tune 转载自: https: ...

  5. Flask源码解读--所有可扩展点

    一.前言 flask中有很多可扩展点(笔者这样称呼),其中包含了信号和请求钩子,这些信号和钩子有什么用呢?其主要作用用于帮助我们进行程序的耦合性,当然还可以让我们自定义一些行为.话不多说,通过阅读源码 ...

  6. Flask源码解读(一)

    Flask是一个使用 Python 编写的轻量级 Web 应用框架.Flask 本身只是 Werkezug 和 Jinja2 的之间的桥梁,前者实现一个合适的 WSGI 应用,后者处理模板. 当然, ...

  7. mybatis源码解读(四)——事务的配置

    上一篇博客我们介绍了mybatis中关于数据源的配置原理,本篇博客介绍mybatis的事务管理. 对于事务,我们是在mybatis-configuration.xml 文件中配置的: 关于解析 < ...

  8. go语言 nsq源码解读四 nsqlookupd源码options.go、context.go和wait_group_wrapper.go

    本节会解读nsqlookupd.go文件中涉及到的其中三个文件:options.go.context.go和wait_group_wrapper.go. options.go 123456789101 ...

  9. MFC源码解读(一)最原始一个MFC程序,手写不用向导

    从这一篇开始,详细记录一下MFC的源码解读 四个文件,分别为: stdafx.h,stdafx.cpp,hello.h,hello.cpp 代码如下: //stdafx.h #include < ...

随机推荐

  1. python使用kazoo操作zookeeper时候出现的"kazoo.exceptions.ConnectionLoss"错误

    在往zk中写入数据的时候,突然遇到 “kazoo.exceptions.ConnectionLoss“错误,然而对zk链接进行检查,在set之前状态是”CONNECT“. 经过测试后发现是因为写入的字 ...

  2. React入门理解demo

    1.React文档结构 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...

  3. Shell基本语法---shell脚本的输入以及脚本拥有特效地输出

    shell脚本的输入 语法:read -参数 -p:给出提示符.默认不支持"\n"换行 -s:隐藏输入的内容 -t:给出等待的时间,超时会退出read,单位是秒 -n:限制读取字符 ...

  4. 【iOS】获取视图的中心和宽高

    示例代码: NSLog(@"%f, %f", self.view.center.x, self.view.center.y); NSLog(@"%f, %f", ...

  5. 【Python】Django 的邮件引擎用法详解!!(调用163邮箱为例)

    1. send_mall()方法介绍 位置: 在django.core.mail模块提供了send_mail()来发送邮件. 方法参数: send_mail(subject, message, fro ...

  6. c#小灶——使用visual studio编写第一个程序

    虽然,写程序有文本编辑器和编译器就已经足够,但是,我们为了增加工作效率还是要使用IDE. 我们接下来所有的教程都将会在visual studio中实现,visual studio简称vs,是微软开发的 ...

  7. 【有容云】PPT | 容器落地之二三事儿

    编者注: 本文为10月29日有容云联合创始人兼研发副总裁江松在 Docker Live时代线下系列-广州站中演讲的PPT,本次线下沙龙为有容云倾力打造Docker Live时代系列主题线下沙龙,每月一 ...

  8. c++随笔之编译器编译原理

    /* C++编译器原理:1)首先明白声明与定义是两个不同的概念 extern int i;是声明,int i;是定义 函数就更简单了2)编译分为: 预编译:将宏替换,include等代码拷贝过来 编译 ...

  9. 结构型设计模式——适配器模式(Go)

    适配器模式: 适配器模式是用于当别人提供的对象或接口中的方法或者其它属性啥的和我们的重复了,或者看的不顺眼.名字太长了记不住,而将其包装到一个对象中,然后通过你感觉自己舒服的方式或者方法名字去间接的调 ...

  10. Redis回顾

    之前有两篇文章着重介绍了redis集群的搭建和redis与spring的整合,一个月过去了,现在有些忘记了,今天又拿过来稳固一下,发现有很多的东西都忘记了. 资料汇总下载 首先安装ruby环境 安装过 ...