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

 2023-09-11 阅读 19 评论 0

摘要:写文章其实很费力,你的「在看」很重要。前言在面试 Python web 方面的工作时,如果你说自己阅读过 Flask 源码,那么 Flask 上下文机制的实现原理肯定会被问到,本篇文章就来剖析一下 Flask 上下文机制。Flask 上下文作用什么是上下文?日常生

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

前言

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

Flask 上下文作用

什么是上下文?

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

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

flask run命令?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

flask框架。可以发现,这些上下文的实现都使用了 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 库,本文以线程为主讨论对象。

python源码剖析在线、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 对象。

结尾

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

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

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

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

参考:

flask 源码解析:上下文

python3源码剖析新版?What is the meaning of a single and a double underscore before an object name?

Python 文档:Private Variables

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/43153.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息