本地线程

Threading Local

为什么需要 Threading Local

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦:

  1. def process_student(name):
  2. std = Student(name)
  3. # std是局部变量,但是每个函数都要用它,因此必须传进去:
  4. do_task_1(std)
  5. do_task_2(std)
  6. def do_task_1(std):
  7. do_subtask_1(std)
  8. do_subtask_2(std)
  9. def do_task_2(std):
  10. do_subtask_2(std)
  11. do_subtask_2(std)

每个函数一层一层调用都这么传参数那还得了?用全局变量?也不行,因为每个线程处理不同的 Student 对象,不能共享。

如果用一个全局 dict 存放所有的 Student 对象,然后以 thread 自身作为 key 获得线程对应的 Student 对象如何?

  1. global_dict = {}
  2. def std_thread(name):
  3. std = Student(name)
  4. # 把std放到全局变量global_dict中:
  5. global_dict[threading.current_thread()] = std
  6. do_task_1()
  7. do_task_2()
  8. def do_task_1():
  9. # 不传入std,而是根据当前线程查找:
  10. std = global_dict[threading.current_thread()]
  11. ...
  12. def do_task_2():
  13. # 任何函数都可以查找出当前线程的std变量:
  14. std = global_dict[threading.current_thread()]
  15. ...

这种方式理论上是可行的,它最大的优点是消除了std对象在每层函数中的传递问题,但是,每个函数获取std的代码有点丑。

Threading Local 的使用

Threading Local 应运而生,不用查找 dictThreadLocal 帮你自动做这件事:

  1. import threading
  2. # 创建全局ThreadLocal对象:
  3. local_school = threading.local()
  4. def process_student():
  5. # 获取当前线程关联的student:
  6. std = local_school.student
  7. print('Hello, %s (in %s)' % (std, threading.current_thread().name))
  8. def process_thread(name):
  9. # 绑定ThreadLocal的student:
  10. local_school.student = name
  11. process_student()
  12. t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
  13. t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
  14. t1.start()
  15. t2.start()
  16. t1.join()
  17. t2.join()

执行结果:

  1. Hello, Alice (in Thread-A)
  2. Hello, Bob (in Thread-B)

全局变量 local_school 就是一个ThreadLocal对象,每个 Thread 读写的是自己线程的 student属性,互不影响,也不用管理锁的问题,ThreadLocal内部会处理。

可以理解为全局变量 local_school 是一个dict,key 为线程id,value 是线程局部变量的字典

ThreadLocal 最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

Threading Local 的实现

参考如下:
threading.local()源码分析

Werkzeug Local

为什么需要 Werkzeug Local

使用 ThreadLocal 对象虽然可以基于线程存储全局变量,但是在Web应用中可能会存在如下问题:

  1. 有些应用使用的是 greenlet 协程 ,这种情况下无法保证协程之间数据的隔离,因为不同的协程可以在同一个线程当中
  2. 即使使用的是线程,WSGI应用也无法保证每个http请求使用的都是不同的线程,因为后一个http请求可能使用的是之前的http请求的线程,这样的话存储于 ThreadLocal 中的数据可能是之前残留的数据

为了解决上述问题,Werkzeug开发了自己的local对象,这也是为什么我们需要Werkzeug的local对象

Werkzeug Local 的使用

先举一个简单的示例:

  1. from werkzeug.local import Local, LocalManager
  2. local = Local()
  3. local_manager = LocalManager([local])
  4. def application(environ, start_response):
  5. local.request = request = Request(environ)
  6. ...
  7. # make_middleware会确保当request结束时,所有存储于local中的对象的reference被清除
  8. application = local_manager.make_middleware(application)
  • 首先 Local 对象需要通过 LocalManager 来管理,初次生成 LocalManager 对象需要传一个list类型的参数,list中是Local对象,当有新的Local对象时,可以通过 local_manager.locals.append() 来添加。而当LocalManager对象清理的时候会将所有存储于 locals 中的当前 context 的数据都清理掉
  • 上例中当 local.request 被赋值之后,其可以在当前 context 中作为全局数据使用
  • 所谓当前 context(the same context) 意味着是在同一个greenlet(如果有)中,也就肯定是在同一个线程当中

那么Werkzeug的Local对象是如何实现这种在相同的 context 环境下保证数据的全局性和隔离性的呢?

Werkzeug Local 的实现

我们先来看下源代码

  1. # 有greenlet的情况下,get_indent实际获取的是greenlet的id
  2. # 没有greenlet的情况下获取的是thread id
  3. try:
  4. from greenlet import getcurrent as get_ident
  5. except ImportError:
  6. try:
  7. from thread import get_ident
  8. except ImportError:
  9. from _thread import get_ident
  10. class Local(object):
  11. __slots__ = ('__storage__', '__ident_func__')
  12. def __init__(self):
  13. object.__setattr__(self, '__storage__', {})
  14. object.__setattr__(self, '__ident_func__', get_ident)
  15. def __iter__(self):
  16. return iter(self.__storage__.items())
  17. # 当调用Local对象时,返回对应的LocalProxy
  18. def __call__(self, proxy):
  19. """Create a proxy for a name."""
  20. return LocalProxy(self, proxy)
  21. # Local类中特有的method,用于清空greenlet id或线程id对应的dict数据
  22. def __release_local__(self):
  23. self.__storage__.pop(self.__ident_func__(), None)
  24. def __getattr__(self, name):
  25. try:
  26. return self.__storage__[self.__ident_func__()][name]
  27. except KeyError:
  28. raise AttributeError(name)
  29. def __setattr__(self, name, value):
  30. ident = self.__ident_func__()
  31. storage = self.__storage__
  32. try:
  33. storage[ident][name] = value
  34. except KeyError:
  35. storage[ident] = {name: value}
  36. def __delattr__(self, name):
  37. try:
  38. del self.__storage__[self.__ident_func__()][name]
  39. except KeyError:
  40. raise AttributeError(name)
  • Werkzeug 使用了自定义的 __storage__ 保存不同线程下的状态
    • key使用的就是 get_indent 函数获取的id(当有greenlet时使用greenlet id,没有则使用thread id
    • value 是一个dict,是 greenlet(或者线程) 对应的 local 存储空间
  • 通过重新实现__getattr__, __setattr__等魔术方法,我们在greenlet或者线程中使用local对象时,实际会自动获取greenlet id(或者线程id),从而获取到对应的 dict 存储空间,再通过name key就可以获取到真正的存储的对象。这个技巧实际上在编写线程安全或协程安全的代码时是非常有用的,即通过线程id(或协程id)来分别存储数据。
  • Werkzeug 使用 get_ident 函数来获取线程/协程标识符
  • Werkzeug 提供了释放本地线程 local 数据的 release_local 方法,如下
    1. >>> loc = Local()
    2. >>> loc.foo = 42
    3. >>> release_local(loc) # release_local实际调用local对象的__release_local__ 方法
    4. >>> hasattr(loc, 'foo')
    5. False

Werkzeug 基于自己实现的 Local 还实现了两种数据结果 :

  • LocalStack : 基于 werkzeug.local.Local 实现的栈结果 , 可以将对象推入 , 弹出 , 也可以快速拿到栈顶对象
  • LocalProxy : 作用和名字一样 , 最标准的代理模式 , 构造此结构时接收一个可以调用的参数 (一般为函数) , 这个函数执行后就是通过 LocalStack 实例化的栈的栈顶对象 ; 对于 LocalProxy 对象的操作实际上都会转发到这个栈顶对象 (也就是一个 thread-local 对象) 上面

Werkzeug LocalStack 的实现

LocalStackLocal 对象类似,都是可以基于 Greenlet 协程或者线程进行全局存储的存储空间(实际 LocalStack 是对 Local 进行了二次封装),区别在于其数据结构是栈的形式。示例如下:

  1. >>> ls = LocalStack()
  2. >>> ls.push(42)
  3. >>> ls.top
  4. 42
  5. >>> ls.push(23)
  6. >>> ls.top
  7. 23
  8. >>> ls.pop()
  9. 23
  10. >>> ls.top
  11. 42
  • 从示例看出 Local 对象存储的时候是类似字典的方式,需要有 key 和 value,而 LocalStack 是基于栈的,通过push和pop来存储和弹出数据
  • 另外,当我们想释放存储空间的时候,也可以调用release_local()

LocalStack 实现

  1. class LocalStack(object):
  2. def __init__(self):
  3. self._local = Local()
  4. def __release_local__(self):
  5. self._local.__release_local__()
  6. @property
  7. def __ident_func__(self):
  8. return self._local.__ident_func__
  9. @__ident_func__.setter
  10. def __ident_func__(self, value):
  11. object.__setattr__(self._local, "__ident_func__", value)
  12. def __call__(self):
  13. def _lookup():
  14. rv = self.top
  15. if rv is None:
  16. raise RuntimeError("object unbound")
  17. return rv
  18. return LocalProxy(_lookup)
  19. def push(self, obj):
  20. """Pushes a new item to the stack"""
  21. rv = getattr(self._local, "stack", None)
  22. if rv is None:
  23. self._local.stack = rv = []
  24. rv.append(obj)
  25. return rv
  26. def pop(self):
  27. """Removes the topmost item from the stack, will return the
  28. old value or `None` if the stack was already empty.
  29. """
  30. stack = getattr(self._local, "stack", None)
  31. if stack is None:
  32. return None
  33. elif len(stack) == 1:
  34. release_local(self._local)
  35. return stack[-1]
  36. else:
  37. return stack.pop()
  38. @property
  39. def top(self):
  40. """The topmost item on the stack. If the stack is empty,
  41. `None` is returned.
  42. """
  43. try:
  44. return self._local.stack[-1]
  45. except (AttributeError, IndexError):
  46. return None

LocalStack在Flask框架中会频繁的出现,其 Request ContextApp Context 的实现都是基于 LocalStack

Werkzeug LocalProxy

LocalProxy 用于代理 Local 对象和 LocalStack 对象,而所谓代理就是作为中间的代理人来处理所有针对被代理对象的操作,如下图所示:
Flask 源码解析 03 —— 上下文环境 - 图1

LocalProxy 的使用

初始化 LocalProxy 有三种方式:

  • 通过 Local 或者 LocalStack 对象的__call__ 方法 ```python from werkzeug.local import Local l = Local()

these are proxies

request = l(‘request’) user = l(‘user’)

from werkzeug.local import LocalStack _response_local = LocalStack()

this is a proxy

response = _response_local()

  1. 当我们将对象作为函数调用时,实际调用的是`__call__` 方法,`__call__` 方法会返回一个 LocalProxy 对象
  2. - 通过 `LocalProxy` 类进行初始化
  3. ```python
  4. l = Local()
  5. request = LocalProxy(l, 'request')

实际上这段代码跟第一种方式是等价的,但这种方式是最 原始 的方式,我们在 Local 的源代码实现中看到其__call__ 方法就是通过这种方式生成 LocalProxy

  • 使用callable对象作为参数
    1. request = LocalProxy(get_current_request())

通过传递一个函数,我们可以自定义如何返回 LocalLocalStack 对象

LocalProxy 实现

下面截取LocalProxy的部分代码,我们来进行解析

  1. # LocalProxy部分代码
  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. # "local" is a callable that is not an instance of Local or
  10. # LocalManager: mark it as a wrapped function.
  11. object.__setattr__(self, '__wrapped__', local)
  12. def _get_current_object(self):
  13. """Return the current object. This is useful if you want the real
  14. object behind the proxy at a time for performance reasons or because
  15. you want to pass the object into a different context.
  16. """
  17. # 由于所有Local或LocalStack对象都有__release_local__ 方法
  18. # 所以如果没有该属性就表明self.__local为callable对象
  19. if not hasattr(self.__local, '__release_local__'):
  20. return self.__local()
  21. try:
  22. # 此处self.__local 为 Local 或 LocalStack 对象
  23. return getattr(self.__local, self.__name__)
  24. except AttributeError:
  25. raise RuntimeError('no object bound to %s' % self.__name__)
  26. @property
  27. def __dict__(self):
  28. try:
  29. return self._get_current_object().__dict__
  30. except RuntimeError:
  31. raise AttributeError('__dict__')
  32. def __getattr__(self, name):
  33. if name == '__members__':
  34. return dir(self._get_current_object())
  35. return getattr(self._get_current_object(), name)
  36. def __setitem__(self, key, value):
  37. self._get_current_object()[key] = value
  38. def __delitem__(self, key):
  39. del self._get_current_object()[key]
  40. if PY2:
  41. __getslice__ = lambda x, i, j: x._get_current_object()[i:j]
  42. def __setslice__(self, i, j, seq):
  43. self._get_current_object()[i:j] = seq
  44. def __delslice__(self, i, j):
  45. del self._get_current_object()[i:j]
  46. # 截取部分操作符代码
  47. __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
  48. __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
  49. __str__ = lambda x: str(x._get_current_object())
  50. __lt__ = lambda x, o: x._get_current_object() < o
  51. __le__ = lambda x, o: x._get_current_object() <= o
  52. __eq__ = lambda x, o: x._get_current_object() == o
  • 首先在__init__ 方法中传递的 local 参数会被赋予属性 _LocalProxy__local,该属性可以通过self.__local 进行访问,关于这一点可以看 StackOverflow的问题回答
  • LocalProxy通过_get_current_object来获取代理的对象。需要注意的是当初始化参数为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。
  • 重载了绝大多数操作符,以便在调用LocalProxy的相应操作时,通过_get_current_object 方法来获取真正代理的对象,然后再进行相应操作

为什么要使用LocalProxy

可是说了这么多,为什么一定要用proxy,而不能直接调用Local或LocalStack对象呢?这主要是在有多个可供调用的对象的时候会出现问题,如下图:
Flask 源码解析 03 —— 上下文环境 - 图2

我们再通过下面的代码也许可以看出一二:

  1. # use Local object directly
  2. from werkzeug.local import LocalStack
  3. user_stack = LocalStack()
  4. user_stack.push({'name': 'Bob'})
  5. user_stack.push({'name': 'John'})
  6. def get_user():
  7. # do something to get User object and return it
  8. return user_stack.pop()
  9. # 直接调用函数获取user对象
  10. user = get_user()
  11. print user['name']
  12. print user['name']

打印结果是:

  1. John
  2. John

再看下使用LocalProxy

  1. # use LocalProxy
  2. from werkzeug.local import LocalStack, LocalProxy
  3. user_stack = LocalStack()
  4. user_stack.push({'name': 'Bob'})
  5. user_stack.push({'name': 'John'})
  6. def get_user():
  7. # do something to get User object and return it
  8. return user_stack.pop()
  9. # 通过LocalProxy使用user对象
  10. user = LocalProxy(get_user)
  11. print user['name']
  12. print user['name']

打印结果是:

  1. John
  2. Bob

怎么样,看出区别了吧,直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用 Proxy,每次调用操作符(这里 []操作符 用于获取属性),都会重新获取user,从而实现了动态更新user的效果。见下图:
Flask 源码解析 03 —— 上下文环境 - 图3
Flask以及Flask的插件很多时候都需要这种动态更新的效果,因此LocalProxy就会非常有用了。

上下文

本地线程是 Flask 中非常重要的一部分 , 因为在请求处理时 , 为了解决请求对象在每一个视图函数传递 (意味着每个视图函数需要像 Django 那样添加一个 request 参数) 的问题 , Flask 巧妙地使用上下文把某些对象变为全局可访问 (实际上是特定环境的局部对象的代理) , 再配合本地线程 , 这样每个线程看到的上下文对象都是不同的

在计算机中,相对于进程而言,上下文就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。可以理解上下文是环境的一个快照,是一个用来保存状态的对象。在程序中我们所写的函数大都不是单独完整的,在使用一个函数完成自身功能的时候,很可能需要同其他的部分进行交互,需要其他外部环境变量的支持,上下文就是给外部环境的变量赋值,使函数能正确运行。

Flask提供了两种上下文,一种是应用上下文(Application Context),一种是请求上下文(Request Context)。

请求上下文

请求上下文示例

  1. from flask import request
  2. @app.route('/')
  3. def index():
  4. user_agent = request.headers.get('User-Agent')
  5. return '<p>Your browser is %s</p>' % user_agent

其流程是这样的 :

  • 用户访问产生请求
  • 在发生请求的过程中向 _request_ctx_stack 推入这个请求上下文对象 , 它会变成栈顶 , request 就会成为这个请求上下文 , 也就包含了本次请求相关的信息和数据
  • 在视图函数中就可以使用 request.args.get('User-Agent') 获取请求信息

Flask 中四种请求hook

Flask 中有四种请求hook

  • 第一次请求处理之前的 hook 函数,通过 before_first_request 定义
  • 每个请求处理之前的 hook 函数,通过 before_request 定义
  • 每个请求正常处理之后的 hook 函数,通过 after_request 定义
  • 不管请求是否异常都要执行的 teardown_request hook 函数
  1. from flask import Flask, g, request
  2. app = Flask(__name__)
  3. @app.before_request
  4. def before_request():
  5. print 'before request started'
  6. print request.url
  7. @app.before_request
  8. def before_request2():
  9. print 'before request started 2'
  10. print request.url
  11. g.name="SampleApp"
  12. @app.after_request
  13. def after_request(response):
  14. print 'after request finished'
  15. print request.url
  16. response.headers['key'] = 'value'
  17. return response
  18. @app.teardown_request
  19. def teardown_request(exception):
  20. print 'teardown request'
  21. print request.url
  22. @app.route('/')
  23. def index():
  24. return 'Hello, %s!' % g.name
  25. if __name__ == '__main__':
  26. app.run(host='0.0.0.0', debug=True)

访问 http://localhost:5000/后,会在控制台输出:

  1. before request started
  2. http://localhost:5000/
  3. before request started 2
  4. http://localhost:5000/
  5. after request finished
  6. http://localhost:5000/
  7. teardown request
  8. http://localhost:5000/

请求上下文实现

当请求进入时,从Flask.__call__() 开始,然后会在 wsgi_app() 方法中调用 Flask.request_context() 方法实例化 RequestContext 类作为请求上下文对象,然后调用 RequestContext 实例的 push() 方法来推入请求上下文堆栈。

wsgi_app 源码:

  1. def wsgi_app(self, environ, start_response):
  2. # 实例化请求上下文对象
  3. ctx = self.request_context(environ)
  4. error = None
  5. try:
  6. try:
  7. # 将请求上下文对象压入栈中,在这之前会先将应用上下文压入栈中
  8. ctx.push()
  9. # 返回response对象
  10. response = self.full_dispatch_request()
  11. except Exception as e:
  12. error = e
  13. response = self.handle_exception(e)
  14. except:
  15. error = sys.exc_info()[1]
  16. raise
  17. # 调用BaseResponse的__call__方法
  18. # 交给WSGI服务器处理
  19. return response(environ, start_response)
  20. finally:
  21. if self.should_ignore_error(error):
  22. error = None
  23. ctx.auto_pop(error)

RequestContext 类实例化方法主要实例化Request对象、Session对象(此时为 None,会在push() 时创建)以及将Request对象与URL连接

  1. # ctx = self.request_context(environ)
  2. # environ是由WSGIRequestHandler.make_environ()制造而来
  3. class RequestContext(object):
  4. """
  5. 请求上下文中包含了请求相关的所有信息
  6. """
  7. def __init__(self, app, environ, request=None):
  8. # Flask应用实例
  9. self.app = app
  10. if request is None:
  11. # 实例化Request对象
  12. request = app.request_class(environ)
  13. self.request = request
  14. # 为请求创建一个URL适配器
  15. self.url_adapter = app.create_url_adapter(self.request)
  16. self.flashes = None
  17. self.session = None
  18. # 一个隐式的应用上下文栈
  19. self._implicit_app_ctx_stack = []
  20. # 显示上下文是否被保留
  21. self.preserved = False
  22. # remembers the exception for pop if there is one in case the context
  23. # preservation kicks in.
  24. self._preserved_exc = None
  25. # 请求后执行函数
  26. self._after_request_functions = []
  27. # 将Request对象与URL连接
  28. self.match_request()

RequestContext.push() 并不是仅仅将请求上下文压入了栈中 , 同时它还生成了应用上下文并压入了栈中
事实上在 Web 应用环境中 , 请求上下文和应用上下文是一一对应的 , 请求上下文和应用上下文都是本地线程的

  1. class RequestContext(object):
  2. """
  3. 请求上下文中包含了请求相关的所有信息
  4. """
  5. ...
  6. def push(self):
  7. """Binds the request context to the current context."""
  8. # 获取栈顶
  9. top = _request_ctx_stack.top
  10. if top is not None and top.preserved:
  11. top.pop(top._preserved_exc)
  12. # Before we push the request context we have to ensure that there
  13. # is an application context.
  14. app_ctx = _app_ctx_stack.top
  15. if app_ctx is None or app_ctx.app != self.app:
  16. # 生成应用上下文AppContext
  17. app_ctx = self.app.app_context()
  18. # 将应用上下文推入栈中
  19. app_ctx.push()
  20. self._implicit_app_ctx_stack.append(app_ctx)
  21. else:
  22. self._implicit_app_ctx_stack.append(None)
  23. if hasattr(sys, 'exc_clear'):
  24. sys.exc_clear()
  25. # 将请求上下文推入栈中
  26. _request_ctx_stack.push(self)
  27. # 创建session 对象
  28. if self.session is None:
  29. session_interface = self.app.session_interface
  30. self.session = session_interface.open_session(
  31. self.app, self.request
  32. )
  33. if self.session is None:
  34. self.session = session_interface.make_null_session(self.app)

对应 RequestContext.pop() 代码如下,请求上下文和应用上下文同时出栈

  1. class RequestContext(object):
  2. """
  3. 请求上下文中包含了请求相关的所有信息
  4. """
  5. ...
  6. def pop(self, exc=_sentinel):
  7. app_ctx = self._implicit_app_ctx_stack.pop()
  8. try:
  9. clear_request = False
  10. if not self._implicit_app_ctx_stack:
  11. self.preserved = False
  12. self._preserved_exc = None
  13. if exc is _sentinel:
  14. exc = sys.exc_info()[1]
  15. # 执行所有使用teardown_request 钩子注册的函数
  16. self.app.do_teardown_request(exc)
  17. # If this interpreter supports clearing the exception information
  18. # we do that now. This will only go into effect on Python 2.x,
  19. # on 3.x it disappears automatically at the end of the exception
  20. # stack.
  21. if hasattr(sys, "exc_clear"):
  22. sys.exc_clear()
  23. request_close = getattr(self.request, "close", None)
  24. if request_close is not None:
  25. request_close()
  26. clear_request = True
  27. finally:
  28. rv = _request_ctx_stack.pop()
  29. # get rid of circular dependencies at the end of the request
  30. # so that we don't require the GC to be active.
  31. if clear_request:
  32. rv.request.environ["werkzeug.request"] = None
  33. # Get rid of the app as well if necessary.
  34. if app_ctx is not None:
  35. app_ctx.pop(exc)
  36. assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
  37. rv,
  38. self,
  39. )
  40. def auto_pop(self, exc):
  41. # 异常发生时需要保持上下文以便进行相关操作,比如在页面的交互式调试器中执行操作或是测试
  42. if self.request.environ.get("flask._preserve_context") or (
  43. exc is not None and self.app.preserve_context_on_exception
  44. ):
  45. self.preserved = True
  46. self._preserved_exc = exc
  47. # 没有异常发生时调用pop()方法移除上下文
  48. else:
  49. self.pop(exc)

既然是上下文对象 , 也就以为着在 RequestContext 中必然定义了 __enter____exit__ :

  1. def __enter__(self):
  2. # 将RequestContext对象压入栈中并返回
  3. self.push()
  4. return self
  5. def __exit__(self, exc_type, exc_value, tb):
  6. # 关闭上下文环境时从栈中弹出
  7. self.auto_pop(exc_value)
  8. if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
  9. reraise(exc_type, exc_value, tb)

所以我们可以使用 with 来开启上下文环境

  1. from flask import Flask
  2. from flask.globals import _request_ctx_stack
  3. app = Flask(__name__)
  4. # 如果你在请求开始前或者请求结束后查看请求上下文栈中的stack
  5. # 很不幸,请求开始前还没有这一属性
  6. # 请求结束后,这一属性也被销毁,因为请求上下文对象销毁了
  7. with app.test_request_context('/?next=http://example.com/') as rqc:
  8. print(rqc.request)
  9. print(_request_ctx_stack._local.stack)
  10. """
  11. 执行结果:
  12. <Request 'http://localhost/?next=http:%2F%2Fexample.com%2F' [GET]>
  13. [<RequestContext 'http://localhost/?next=http:%2F%2Fexample.com%2F' [GET] of ex1>]
  14. """

应用上下文

应用上下文会按需自动创建和销毁 , 如在将请求上下文对象压入栈中时 , 如果应用上下文栈中没有 , 则会先创建应用上下文 , 它不会在线程间移动 , 并且也不会在请求间共享。

应用上下文源码

  1. class AppContext(object):
  2. def __init__(self, app):
  3. self.app = app
  4. self.url_adapter = app.create_url_adapter(None)
  5. self.g = app.app_ctx_globals_class()
  6. # 引用计数,以追踪被压入栈的次数
  7. self._refcnt = 0
  8. def push(self):
  9. """Binds the app context to the current context."""
  10. self._refcnt += 1
  11. if hasattr(sys, "exc_clear"):
  12. sys.exc_clear()
  13. _app_ctx_stack.push(self)
  14. appcontext_pushed.send(self.app)
  15. def pop(self, exc=_sentinel):
  16. """Pops the app context."""
  17. try:
  18. self._refcnt -= 1
  19. if self._refcnt <= 0:
  20. if exc is _sentinel:
  21. exc = sys.exc_info()[1]
  22. self.app.do_teardown_appcontext(exc)
  23. finally:
  24. rv = _app_ctx_stack.pop()
  25. assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self)
  26. appcontext_popped.send(self.app)
  27. def __enter__(self):
  28. self.push()
  29. return self
  30. def __exit__(self, exc_type, exc_value, tb):
  31. self.pop(exc_value)
  32. if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
  33. reraise(exc_type, exc_value, tb)

总结