写文章其实很费力,你的「在看」很重要。

前言

在面试 Python web 方面的工作时,如果你说自己阅读过 Flask 源码,那么 Flask 上下文机制的实现原理肯定会被问到,本篇文章就来剖析一下 Flask 上下文机制。

Flask 上下文作用

什么是上下文?

日常生活中的上下文:从一篇文章中抽取一段话,你阅读后,可能依旧无法理解这段话中想表达的内容,因为它引用了文章其他部分的观点,要理解这段话,需要先阅读理解这些观点。这些散落于文章的观点就是这段话的上下文。

程序中的上下文:一个函数通常涉及了外部变量 (或方法),要正常使用这个函数,就需要先赋值给这些外部变量,这些外部变量值的集合就称为上下文,自行琢磨一下。

Flask 的视图函数需要知道前端请求的 url、参数以及数据库等应用信息才可以正常运行,要怎么做?

一个粗暴的方法是将这些信息通过传参的方式一层层传到到视图函数,太不优雅。Flask 为此设计出了自己的上下文机制,当需要使用请求信息时,直接 fromflaskimportrequest就可以获得当前请求的所有信息并且在多线程环境下是线程安全的,很酷。

实现这种效果的大致原理其实与 threading.local 实现原理相同,创建一个全局的字典对象,利用线程 id 作为 key,相应的数据作为 value,这样,不同的线程就可以获取专属于自己的数据。

Flask 上下文机制

Flask 上下文定义在 globals.py 上,代码如下。

  1. # flask/globals.py
  2. def _lookup_req_object(name):
  3. top = _request_ctx_stack.top
  4. if top is None:
  5. raise RuntimeError(_request_ctx_err_msg)
  6. return getattr(top, name)
  7. def _lookup_app_object(name):
  8. top = _app_ctx_stack.top
  9. if top is None:
  10. raise RuntimeError(_app_ctx_err_msg)
  11. return getattr(top, name)
  12. def _find_app():
  13. top = _app_ctx_stack.top
  14. if top is None:
  15. raise RuntimeError(_app_ctx_err_msg)
  16. return top.app
  17. # context locals
  18. _request_ctx_stack = LocalStack()
  19. _app_ctx_stack = LocalStack()
  20. current_app = LocalProxy(_find_app)
  21. # partial()构建一个偏函数
  22. request = LocalProxy(partial(_lookup_req_object, "request"))
  23. session = LocalProxy(partial(_lookup_req_object, "session"))
  24. g = LocalProxy(partial(_lookup_app_object, "g"))

Flask 中看似有多个上下文,但其实都衍生于 _request_ctx_stack_app_ctx_stack_request_ctx_stack是请求上下文, _app_ctx_stack是应用上下文。

常用的 request 和 session 衍生于 _request_ctx_stack,current_app 和 g 衍生于 _app_ctx_stack

可以发现,这些上下文的实现都使用了 LocalStack 和 LocalProxy,这两个类的实现在 werkzeug 中,在实现这两个类之前,需要先理解 Local 类,代码如下。

  1. # werkzeug/local.py
  2. class Local(object):
  3. __slots__ = ("__storage__", "__ident_func__")
  4. def __init__(self):
  5. # 调用__setattr__()方法设置值。
  6. object.__setattr__(self, "__storage__", {})
  7. # 获得线程id
  8. object.__setattr__(self, "__ident_func__", get_ident)
  9. # 迭代不同线程对应的字典对象
  10. def __iter__(self):
  11. return iter(self.__storage__.items())
  12. # 返回 LocalProxy 对象
  13. def __call__(self, proxy):
  14. """Create a proxy for a name."""
  15. return LocalProxy(self, proxy)
  16. # pop() 清除当前线程保存的数据
  17. def __release_local__(self):
  18. self.__storage__.pop(self.__ident_func__(), None)
  19. def __getattr__(self, name):
  20. try:
  21. # 获得当前线程的数据中对应的值
  22. return self.__storage__[self.__ident_func__()][name]
  23. except KeyError:
  24. raise AttributeError(name)
  25. # 设置线程数据
  26. def __setattr__(self, name, value):
  27. ident = self.__ident_func__()
  28. storage = self.__storage__
  29. try:
  30. storage[ident][name] = value
  31. except KeyError:
  32. storage[ident] = {name: value}
  33. # 删除线程数据
  34. def __delattr__(self, name):
  35. try:
  36. del self.__storage__[self.__ident_func__()][name]
  37. except KeyError:
  38. raise AttributeError(name)

Local 类的代码很好理解, __init__()方法创建了 __storage__用于存储数据与 __ident_func__用于获得线程 id,这里使用 object.__setattr__()来实现赋值,这样做是不是有什么深意?

没有,只是一个 ticks。

因为 Local 类本身重写了 __setattr__()方法,如果直接使用 self.__storage__={}进行赋值,就会调用重写的 __setattr__方法,导致报错,所以赋值要使用父类 object 的 __setattr__object.__setattr__('name','二两')object.name='二两'效果完成一样,不用觉得太高深。

Local 类重写了 __getattr____setattr____delattr__,从而自定义了 Local 对象属性访问、设置与删除对应的操作,这些方法都通过 __ident_func__()方法获取当前线程 id 并以此作为 key 去操作当前线程对应的数据,Local 通过这种方式实现了多线程数据的隔离。

值得一提, __ident_func__()也可以获得协程的 id,但需要安装 greenlet 库,本文以线程为主讨论对象。

LocalStack 类是基于 Local 类实现的栈结构,代码如下。

  1. # werkzeug/local.py
  2. # 构建一个栈
  3. class LocalStack(object):
  4. def __init__(self):
  5. # 实例化 Local类
  6. self._local = Local()
  7. # 清除当前线程保存的数据
  8. def __release_local__(self):
  9. self._local.__release_local__()
  10. @property
  11. def __ident_func__(self): # 获得当前线程id
  12. return self._local.__ident_func__
  13. @__ident_func__.setter
  14. def __ident_func__(self, value):
  15. object.__setattr__(self._local, "__ident_func__", value)
  16. def __call__(self):
  17. def _lookup():
  18. rv = self.top
  19. if rv is None:
  20. raise RuntimeError("object unbound")
  21. return rv
  22. return LocalProxy(_lookup)
  23. def push(self, obj):
  24. # 利用list来构建一个栈
  25. rv = getattr(self._local, "stack", None)
  26. if rv is None:
  27. self._local.stack = rv = []
  28. rv.append(obj)
  29. return rv
  30. def pop(self):
  31. stack = getattr(self._local, "stack", None)
  32. if stack is None:
  33. return None
  34. elif len(stack) == 1:
  35. # release_local()调用的依旧是__release_local__()
  36. release_local(self._local)
  37. return stack[-1]
  38. else:
  39. # 出栈
  40. return stack.pop()
  41. @property
  42. def top(self):
  43. try:
  44. # 获得栈顶数据
  45. return self._local.stack[-1]
  46. except (AttributeError, IndexError):
  47. return None

LocalStack 类的代码简洁易懂,主要的逻辑就实例化 Local 类,获得 local 对象,在 local 对象中添加 stack,以 list 的形式来实现一个栈,至此可知, _request_ctx_stack_app_ctx_stack这两个上下文就是一个线程安装的栈,线程所有的信息都会保存到相应的栈里,直到需要使用时,再出栈获取。

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

  1. # werkzeug/local.py
  2. @implements_bool
  3. class LocalProxy(object):
  4. __slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
  5. def __init__(self, local, name=None):
  6. object.__setattr__(self, "_LocalProxy__local", local)
  7. object.__setattr__(self, "__name__", name)
  8. if callable(local) and not hasattr(local, "__release_local__"):
  9. object.__setattr__(self, "__wrapped__", local)
  10. def _get_current_object(self):
  11. if not hasattr(self.__local, "__release_local__"):
  12. return self.__local() # 获得local对象
  13. try:
  14. return getattr(self.__local, self.__name__)
  15. except AttributeError:
  16. raise RuntimeError("no object bound to %s" % self.__name__)
  17. # ... 省略部分代码
  18. def __getattr__(self, name):
  19. if name == "__members__":
  20. return dir(self._get_current_object())
  21. # 获得local对象中对应name的值
  22. return getattr(self._get_current_object(), name)
  23. def __setitem__(self, key, value):
  24. # 为local对象赋值
  25. self._get_current_object()[key] = value
  26. def __delitem__(self, key):
  27. del self._get_current_object()[key]
  28. __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
  29. # ... 省略部分代码

LocalProxy 类在 __init__()方法中将 local 实例赋值给 _LocalProxy__local并在后续的方法中通过 __local的方式去操作它,这其实也是一个 ticks。

因为 LocalProxy 类重写了 __setattr__方法,所以不能直接复制,此时要通过 object.__setattr__()进行赋值。根据 Python 文档可知 (参考小节会给出出处 url),任何形式上以双下划线开头的私有变量 __xxx,在文本上均会替换成 _classname__xxx,而 __setattr__会直接操作文本,所以给 _LocalProxy__local赋值。

LocalProxy 类后面的逻辑其实都是一层代理,将真正的处理交个 local 对象。

结尾

考虑到字数,上下文的内容拆成 2 篇,下篇会提出几个问题并给出相应的回答与看法。

1.Python 中有 thread.local 了,werkzeug 为什么还要自己弄一个 Local 类来存储数据?2. 为什么不直接使用 Local?而要通过 LocalStack 类将其封装成栈的操作?3. 为什么不直接使用 Local?而要通过 LocalProxy 类来代理操作?

反正我看源码时会有这样的疑惑,这其实也是设计的精髓,是我们要学习的地方,卖个关子,下篇见。

如果这篇文章对你有所启发,点个「在看」支持二两。

参考:

flask 源码解析:上下文

What is the meaning of a single and a double underscore before an object name?

Python 文档:Private Variables

fragment怎么获得上下文环境_Flask 源码剖析 (三):Flask 的上下文机制 (上)相关推荐

  1. boost源码剖析之:多重回调机制signal(下)

    boost源码剖析之:多重回调机制signal(下) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) 在本文的上篇中,我们大刀阔斧的剖析了signal的架构.不过还有 ...

  2. boost源码剖析之:多重回调机制signal(上)

    boost源码剖析之:多重回调机制signal(上) 刘未鹏 C++的罗浮宫(http://blog.csdn.net/pongba) boost库固然是技术的宝库,却更是思想的宝库.大多数程序员都知 ...

  3. Swoft 源码剖析 - Swoft 中的注解机制

    作者:bromine 链接:https://www.jianshu.com/p/ef7... 來源:简书 著作权归作者所有,本文已获得作者授权转载,并对原文进行了重新的排版. Swoft Github ...

  4. STL源码剖析---红黑树原理详解上

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7740956 一.红黑树概述 红黑树和我们以前学过的AVL树类似,都是在进 ...

  5. MySQL · 引擎介绍 · Sphinx源码剖析(三)

    在本节中我会介绍Sphinx在构建索引之前做的一些事情,主要是从mysql拉取数据保存,然后分词排序保存到内存等等一系列的操作.下面是几个相关指令 sql_query = \SELECT id, gr ...

  6. Muduo库源码剖析(三)——获取线程tid方法

    相关知识点 __thread __thread修饰 表示使用 线程局部存储机制(threadlocal 机制) ,即会为修饰的变量在当前线程存储一份copy,别的线程是看不到这个变量的修改 __thr ...

  7. 源码 状态机_阿里中间件seata源码剖析七:saga模式实现

    saga模式是分布式事务中使用比较多的一种模式,他主要应用在长流程的服务,对一个全局事务,如果某个节点抛出了异常,则从这个节点往前依次回滚或补偿事务.今天我们就来看看它的源码实现. 状态机初始化 在之 ...

  8. pymavlink 源码剖析(二)之生成代码

    文章目录 1 引言 2 C 代码生成 3 generate_one 函数分析 4 MAVTemplate 5 头文件生成 相关: pymavlink 源码剖析(一)之XML文件的数据解析 MAVLin ...

  9. STL源码剖析学习之increment、decrement、dereference实现源码

    //STL之increment.decrement.dereference实现源码 //学习目的:STL实现原理.操作符(++i,i++,*等操作符的重载) //increment/dereferen ...

最新文章

  1. 【2020新书推荐】Introduction to Deep Learning
  2. php文件数组,从文件到数组php
  3. OpenCV 图像平移
  4. 基于socket的线上聊天框
  5. 通讯实例 modbus_实例讲解PLC实现modbus通讯
  6. oracle 按月累计求和,SQL Cumulative Sum累积求和
  7. Java开发人员必备的7大技能,每一个都必不可少
  8. spring编程式事务控制
  9. android 自定义flowlayout,Android 自定义ViewGroup之实现FlowLayout-标签流容器
  10. Aptana Studio 3 汉化简体中文版
  11. Objective-C征途:Hello Objective-C
  12. 精彩回放 | 玩转 VS Code 物联网开发
  13. matlab制作强光效果代码,自己动手做一个雷达PPI显示器的动态效果图(附Matlab代码)...
  14. IntelliJ IDEA类注释模板设置
  15. 5KPlayer:跨平台支持 AirPlay 无线串流 / 下载在线视频
  16. 菜鸟驿站是什么快递_菜鸟驿站是什么快递
  17. Problem A: 素MM
  18. 仪器仪表通讯协议1: CJ/T188水表通讯协议
  19. lgv50进入工程模式_LG手机工程模式进入方法及菜单指令翻译(适用G6、G7、V20、V30等)...
  20. webSpider----request

热门文章

  1. 在InternetExplorer.Application中显示本地图片
  2. SQL的四种连接-左外连接、右外连接、内连接、全连接(转)
  3. Java中的堆分配参数总结《对Java的分析总结》(二)
  4. Vue填坑(v-model和:model)
  5. 大数据,TB、PB、EB
  6. window环境下安装Python2和Python3
  7. Numpy 通用函数
  8. 变压器的同名端,以及判别方法
  9. 2014522920145316《信息安全系统设计基础》实验一 开发环境的熟悉
  10. 【软件工程第三次作业】