"contextvars" --- 上下文变量
****************************

======================================================================

本模块提供了相关API用于管理、存储和访问上下文相关的状态。 "ContextVar"
类用于声明 *上下文变量* 并与其一起使用。函数 "copy_context()"  和类
"Context" 用于管理当前上下文和异步框架中。

当在并发代码中使用时，有状态的上下文管理器应当使用上下文变量而不是
"threading.local()" 以防止它们的状态意外地泄露到其他代码中。

更多信息参见  **PEP 567** 。

Added in version 3.7.


上下文变量
==========

class contextvars.ContextVar(name[, *, default])

   此类用于声明一个新的上下文变量，如:

      var: ContextVar[int] = ContextVar('var', default=42)

   *name* 参数用于内省和调试，必需。

   调用 "ContextVar.get()"  时，如果上下文中没有找到此变量的值，则返回
   可选的仅命名参数 *default*  。

   **重要：** 上下文变量应该在顶级模块中创建，且永远不要在闭包中创建。
   "Context" 对象拥有对上下文变量的强引用，这可以让上下文变量被垃圾收
   集器正确回收。

   name

      上下文变量的名称，只读属性。

      Added in version 3.7.1.

   get([default])

      返回当前上下文中此上下文变量的值。

      如果当前上下文中此变量没有值，则此方法会:

      * 如果提供了 *default*，返回其值；或者

      * 返回上下文变量本身的默认值， 如果创建此上下文变量时提供了默认
        值；或者

      * 抛出  "LookupError" 异常。

   set(value)

      调用此方法设置上下文变量在当前上下文中的值。

      必选参数 *value* 是上下文变量的新值。

      返回一个 "Token"  对象，可通过 "ContextVar.reset()"  方法将上下
      文变量还原为之前某个状态。

   reset(token)

      将上下文变量重置为调用 "ContextVar.set()" 之前、创建 *token* 时
      候的状态。

      例如：

         var = ContextVar('var')

         token = var.set('new value')
         # 使用 'var' 的代码；var.get() 将返回 'new value'。
         var.reset(token)

         # 在重置调用之后 var 将不再有值，
         # 因此 var.get() 将引发一个 LookupError。

class contextvars.Token

   "ContextVar.set()" 方法返回 *Token* 对象。此对象可以传递给
   "ContextVar.reset()" 方法用于将上下文变量还原为调用 *set* 前的状态
   。

   var

      只读属性。指向创建此 token 的 "ContextVar" 对象。

   old_value

      一个只读属性。 会被设为在创建此令牌的 "ContextVar.set()" 方法调
      用之前该变量所具有的值。 如果调用之前变量没有设置值则它会指令
      "Token.MISSING"。

   MISSING

      "Token.old_value" 会用到的一个标记对象。


手动上下文管理
==============

contextvars.copy_context()

   返回当前上下文中 "Context" 对象的拷贝。

   以下代码片段会获取当前上下文的拷贝并打印设置到其中的所有变量及其值:

      ctx: Context = copy_context()
      print(list(ctx.items()))

   此函数具有 *O*(1) 复杂度，也就是说对于只包含几个上下文变量和很多上
   下文变量的情况运行速度是相同的。

class contextvars.Context

   "ContextVars" 与其值的映射。

   "Context()" 创建一个不包含任何值的空上下文。如果要获取当前上下文的
   拷贝，使用 "copy_context()" 函数。

   每个线程将有一个不同的最高层级 "Context" 对象。 这意味着当在不同的
   线程中赋值时 "ContextVar" 对象的行为方式与 "threading.local()" 类似
   。

   Context 实现了 "collections.abc.Mapping" 接口。

   run(callable, *args, **kwargs)

      按照 *run* 方法中的参数在上下文对象中执行 "callable(*args,
      **kwargs)" 代码。返回执行结果，如果发生异常，则将异常透传出来。

      *callable* 对上下文变量所做的任何修改都会保留在上下文对象中:

         var = ContextVar('var')
         var.set('spam')

         def main():
             # 在调用 'copy_context()' 和 'ctx.run(main)' 之前
             # 'var' 被设为 'spam'，因此：
             # var.get() == ctx[var] == 'spam'

             var.set('ham')

             # 现在，将 'var' 设为 'ham' 之后：
             # var.get() == ctx[var] == 'ham'

         ctx = copy_context()

         # 'main' 函数对 'var' 的任何修改
         # 都将包含在 'ctx' 中。
         ctx.run(main)

         # 'main()' 函数是在 'ctx' 上下文中运行的，
         # 因此对 'var' 的修改包含在其中：
         # ctx[var] == 'ham'

         # 不过，在 'ctx' 之外，'var' 仍然被设为 'spam'：
         # var.get() == 'spam'

      当在多个系统线程或者递归调用同一个上下文对象的此方法，抛出
      "RuntimeError" 异常。

   copy()

      返回此上下文对象的浅拷贝。

   var in context

      如果 *context* 中含有名称为 *var* 的变量，返回 "True"， 否则返回
      "False"。

   context[var]

      返回名称为 *var* 的 "ContextVar" 变量。如果上下文对象中不包含这
      个变量，则抛出 "KeyError" 异常。

   get(var[, default])

      如果 *var* 在上下文对象中具有值则返回 *var* 的值。 在其他情况下
      返回 *default*。 如果未给出 *default* 则返回 "None"。

   iter(context)

      返回一个存储在上下文对象中的变量的迭代器。

   len(proxy)

      返回上下文对象中所设的变量的数量。

   keys()

      返回上下文对象中的所有变量的列表。

   values()

      返回上下文对象中所有变量值的列表。

   items()

      返回包含上下文对象中所有变量及其值的 2 元组的列表。


asyncio 支持
============

上下文变量在 "asyncio" 中有原生的支持并且无需任何额外配置即可被使用。
例如，以下是一个简单的回显服务器，它使用上下文变量来让远程客户端的地址
在处理该客户端的 Task 中可用:

   import asyncio
   import contextvars

   client_addr_var = contextvars.ContextVar('client_addr')

   def render_goodbye():
       # The address of the currently handled client can be accessed
       # without passing it explicitly to this function.

       client_addr = client_addr_var.get()
       return f'Good bye, client @ {client_addr}\r\n'.encode()

   async def handle_request(reader, writer):
       addr = writer.transport.get_extra_info('socket').getpeername()
       client_addr_var.set(addr)

       # In any code that we call is now possible to get
       # client's address by calling 'client_addr_var.get()'.

       while True:
           line = await reader.readline()
           print(line)
           if not line.strip():
               break

       writer.write(b'HTTP/1.1 200 OK\r\n')  # status line
       writer.write(b'\r\n')  # headers
       writer.write(render_goodbye())  # body
       writer.close()

   async def main():
       srv = await asyncio.start_server(
           handle_request, '127.0.0.1', 8081)

       async with srv:
           await srv.serve_forever()

   asyncio.run(main())

   # To test it you can use telnet or curl:
   #     telnet 127.0.0.1 8081
   #     curl 127.0.0.1:8081
