Inside Flask - globals 全局变量(对象代理)

框架是一个容器,在框架内编程,一般是要遵守框架的约定和使用模式。通常这样的模式是 IoC,即由框架调用用户的代码,而不是用户调用框架。框架需要记录当前的状态,并提供给用户代码使用。常用的 Jsp Servelet 、ASP.net 等,将请求和状态封装为向用户代码提供的 request 、session 等对象。在 flask 中,完成这些工作的是上下文 ctx (context) 和 globals 的全局对象。

flask/globals.py 文件中只使用了简单的 30 行左右的代码构建 flask 使用到的全局对象(更准确地说是对象代理)。虽说这些对象是全局可访问的,其实它们是线程隔离,即两个不同的请求,不同使用到相同的对象。Python 的强大动态语言特性,使得不同运行线程上的代码总是能正确地获取到该线程上对应的对象实例。

整个文件包括了几个对象 ::

_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'))

LocalStack 类是与当前运行线程绑定的栈,LocalProxy 是对象代理,均来自 werkzeug 库。这两个类的设计原理是理解 flask 的 globals 对象的设计和安全性的关键,因此这个解析一下。

LocalStack

LocalStack 的底层是 werkzeug 里的 Local 类,它提供一个与线程绑定的字典,查看代码如下 ::

class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

原理其实相当简单!

Local 中的 __storage__ 是一个字典,__ident_func__ 是线程(协程)的取 id 函数。get_ident 函数根据采用不同的运行方案而不同,如果使用多线程方式运行服务器,那么用 thread 模块里面的 get_ident 函数,如果是通过 greenlet 协程方式,那么用 from greenlet import getcurrent as get_ident

第二步,Local 的 __getattr____setattr____delattr__ 3 个对属性操作的 magic 方法,在存取数据时,都通过 __storage____ident_func 进行 ::

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] = value
    except KeyError:
        storage[ident] = {name: value}

def __delattr__(self, name):
    try:
        del self.__storage__[self.__ident_func__()][name]
    except KeyError:
        raise AttributeError(name)

因此,每次得到的数据都会是本线程(协程)中的数据,A 线程上服务的用户绝不会拿到 B 线程上服务的用户数据(还涉及到上下文 ctx 的生命周期,没在此处描述)。

LocalStack 是对 Local 的简单包装,以支持以栈的方式读取数据,增加 push pop top 等栈方法。其实它无非是在 Local 加一个 stack 属性,代码为证 (_local 即为被包装的 Local 对象) ::

def push(self, obj):
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
        self._local.stack = rv = []
    rv.append(obj)
    return rv

LocalProxy

LocalProxy 是一个对象代理类,即它会把调用传递到真实后端对象,一个不太恰当的例子如下 ::

a = LocalProxy(func_find_real_obj)
a.do_something() =>  b = func_find_real_obj() => b.do_something()

这个例子与 LocalProxy 不同的地方在于,例子中通过 func_find_real_obj 函数查找真实对象。而 LocalProxy 支持两种查找方法,一是在一个 Local 类对象容器中,找一个名字为 name 的对象;二是当 local 是一个函数时,直接从 local() 函数 取得被代理对象(让取得对象的过程更像是直接取一个变量,而不是调用函数,看起来更有幂等性)。

LocalProxy 的设计关键包括两个方面:(1)如何寻找到被代理对象;(2)如果把调用传递到被代理对象。在 werkzeug 中,LocalProxy 从 Local 类的对象中找被代理对象,然后通过 python 的 magic 方法传到到该对象。

LoalProxy 的 __init__ 方法如下 ::

def __init__(self, local, name=None):
    object.__setattr__(self, '_LocalProxy__local', local)
    object.__setattr__(self, '__name__', name)
    

local 就是保存对象容器的地方,而 name 是所被代理对象的名字。

这里有个奇怪的地方 _LocalProxy__local ,它涉及到python 的私有变量的处理方法,即通过 _classname__spam 形式隐藏变量名,这里把它当作 __local 私有变量即可。

LocalProxy 通过 _get_current_object() 方法取得被代理对象 ::

def _get_current_object(self):
    """Return the current object.  This is useful if you want the real
    object behind the proxy at a time for performance reasons or because
    you want to pass the object into a different context.
    """
    if not hasattr(self.__local, '__release_local__'):
        return self.__local()   # 如果是一个查找被代理对象函数
    try:
        return getattr(self.__local, self.__name__)     # 如果是从 Local 对象取
    except AttributeError:
        raise RuntimeError('no object bound to %s' % self.__name__)

return getattr(self.__local, self.__name__) 就是从一个 Local 类的对象中取得当前线程(协程)里的名字为 self.__name__ 对象(第二种方法不需要这个名字)。

最后,LocalProxy 实现了一大堆的 magic 方法,去调用真实对象 ::

...
@property
def __dict__(self):
    try:
        return self._get_current_object().__dict__
    except RuntimeError:
        raise AttributeError('__dict__')

def __repr__(self):
    try:
        obj = self._get_current_object()
    except RuntimeError:
        return '<%s unbound>' % self.__class__.__name__
    return repr(obj)
...

整合

OK,现在整合一下上面的两个类的设计原理,来看 globals 里面的对象 ::

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

_request_ctx_stack_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'))

这几个都是通过查函数方式取得被代理的真实对象。

OK,问题来了,这里面的都是代理而已,那么真实的被代理对象是哪里来的?答案是它们是 flask 在处理请求的过程中由 flask 生成,然后保存下来的。

_request_ctx_stack 为例说明这个过程。

flask 处理请求时,按照 wsgi 规范, wsgi 框架调用 flask app 的 wsgi_app 函数,即 ::

web 请求 => wsgi 框架包装 =》 wsgi_app()

flask/app.py 中,这个函数前面两行代码,就生成了 _request_ctx_stack ::

def wsgi_app(self, environ, start_response):
    ...
    ctx = self.request_context(environ)
    ctx.push()

其它几个代理的真实对象也差不多是相同的处理流程,就不再讨论。

Inside Flask - globals 全局变量(对象代理)的更多相关文章

  1. Inside Flask - flask.__init__.py 和核心组件

    Inside Flask - flask.__init__.py 和核心组件 简单的示例 首先看看一个简单的示例.使用 Flask ,通常是从 flask 模块导入 Flask . request 等 ...

  2. Inside Flask - signal 信号机制

    Inside Flask - signal 信号机制 singal 在平常的 flask web 开发过程中较少接触到,但对于使用 flask 进行框架级别的开发时,则必须了解相关的工作机制.flas ...

  3. Inside Flask - json 处理

    Inside Flask - json 处理 在处理 web api 时,json 是非常好用的数据交换格式,它结构简单,基本上各种主流的编程语言都有良好的支持工具. flask 中处理 json 时 ...

  4. Inside Flask - 配置的实现

    Inside Flask - 配置的实现 flask 的配置对象 app.config 本身使用很简单,无非就是以字典的形式使用,而它的实现,本身就是以字典的形式的. 在 flask/config.p ...

  5. Inside Flask - app.py - 2

    Inside Flask - app.py - 2 Flask 初始化参数 Flass 类是 Flask 框架的核心,一个 flask 对象处理视图函数注册.URL规则.模板配置.参数设置等等. 一般 ...

  6. Inside Flask - flask 扩展加载过程

    Inside Flask - flask 扩展加载过程 flask 扩展(插件)通常是以 flask_<扩展名字> 为扩展的 python 包名,而使用时,可用 import flask. ...

  7. 在spring中获取代理对象代理的目标对象工具类

    昨天晚上一哥们需要获取代理对象的目标对象,查找了文档发现没有相应的工具类,因此自己写了一个分享给大家.能获取JDK动态代理/CGLIB代理对象代理的目标对象. 问题描述:: 我现在遇到个棘手的问题,要 ...

  8. Inside Flask - app.py - 1

    Inside Flask - app.py - 1 除 werkzeug 和 jinja2 等依赖库外,app.py 是在 Flask 的 __init__.py 中导入的第一个 Flask 自身的模 ...

  9. Inside Flask - Flask 简介

    Inside Flask - Flask 简介 前言 Flask 的设计目标是实现一个 wsgi 的微框架,其核心代码保持简单和可扩展性,很容易学习.对于有一定经验初学者而言,跟着例子和一些书的代码来 ...

随机推荐

  1. fsockopen读取、发送cookie及注意事项 -代码示例

    function httpPost($url, $data,$cookieStr='') { $url_array = parse_url($url); $host = $url_array['hos ...

  2. asp.net中如何防止用户重复点击提交按钮

    asp.net中如何防止用户重复点击提交按钮   asp.net 中防止因为网速慢等影响交互的问题导致用户可能点击多次提交按钮,从而导致数据库中出现多条重复的记录,经过亲自验证在网上找的方法,找到两个 ...

  3. 【iCore3 双核心板】例程四:USART通信实验——通过命令控制LED

    实验指导书及代码包下载: http://pan.baidu.com/s/1pJxluWF iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  4. 【转】AngularJS 取消对 HTML 片段的转义

    今天尝试用 Rails 做后端提供 JSON 格式的数据, AngularJS 做前端处理 JSON 数据,其中碰到 AngularJS 获取的是一段 HTML 文本,如果直接使用 data-ng-b ...

  5. Python3学习(2)-中级篇

    Python3学习(1)-基础篇 Python3学习(2)-中级篇 Python3学习(3)-高级篇 切片:取数组.元组中的部分元素 L=['Jack','Mick','Leon','Jane','A ...

  6. SQL union和union all的区别

    Union因为要进行重复值扫描,所以效率低.如果合并没有刻意要删除重复行,那么就使用Union All  两个要联合的SQL语句 字段个数必须一样,而且字段类型要“相容”(一致): 如果我们需要将两个 ...

  7. WinForm中AssemblyInfo.cs文件参数具体讲解

    在.NET中有一个配置文件AssemblyInfo.cs主要用来设定生成的有关程序集的常规信息dll文件的一些参数,下面是默认的AssemblyInfo.cs文件的内容具体介绍 //是否符合公共语言规 ...

  8. [ZOJ3494]BCD Code

    AC自动机+数位DP. 大致题意: BCD码就是把一个数十进制下的每一位分别用4位的二进制表示. 给你一坨01串,问你在一个区间内,有多少个数的BCD码不包含任何一个字符串. 因为涉及到多个串的匹配问 ...

  9. Spring配置优化_构造器注入+自动装配

    2014-05-16 09:01:08上课内容: 依赖注入的第二种注入方式:构造器注入 创建带参数的构造方法,参数类型为注入类的类型 项目要先添加Spring支持: package com; publ ...

  10. 仿9GAG制作过程(五)

    有话要说: 在做完了数据展示功能之后,就想着完善整个APP.发现现在后台非常的混乱,有好多点都不具备,比方说:图片应该有略缩图和原图,段子.评论.点赞应该联动起来,段子应该有创建时间等. 于是就重新设 ...