"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* は内観やデバッグの目的で使われます。

   オプションのキーワード専用引数 *default* は、現在のコンテキストにそ
   の変数の値が見付からなかったときに "ContextVar.get()" から返されま
   す。

   **重要:** コンテキスト変数は、モジュールのトップレベルで生成する必
   要があり、クロージャの中で作成すべきではありません。"Context" オブ
   ジェクトはコンテキスト変数への強参照を持っており、コンテキスト変数
   がガーベジコレクトされるのを防ぎます。

   "ContextVar" は、格納する値の型について ジェネリック です。

   name

      変数の名前。読み出し専用のプロパティです。

      Added in version 3.7.1.

   get([default])

      現在のコンテキストのコンテキスト変数の値を返します。

      現在のコンテキストのコンテキスト変数に値がなければ、メソッドは:

      * メソッドの *default* 引数に値が指定されていればその値を返しま
        す。さもなければ

      * コンテキスト変数が生成された時にデフォルト値が渡されていれば、
        その値を返します。さもなければ

      * "LookupError" を送出します。

   set(value)

      現在のコンテキストのコンテキスト変数に新しい値を設定する際に呼び
      出します。

      *value* は必須の引数で、コンテキスト変数の新しい値を指定します。

      "Token" オブジェクトを返します。このオブジェクトを
      "ContextVar.reset()" メソッドに渡すことで、以前の値に戻すことが
      できます。

      便利な使い方として、トークンオブジェクトをコンテキストマネージャ
      として使えば、"ContextVar.reset()" を手動で呼び出す必要がなくな
      ります。

         var = ContextVar('var', default='default value')

         with var.set('new value'):
             assert var.get() == 'new value'

         assert var.get() == 'default value'

      これは次のコードの短縮形です。

         var = ContextVar('var', default='default value')

         token = var.set('new value')
         try:
             assert var.get() == 'new value'
         finally:
             var.reset(token)

         assert var.get() == 'default value'

      Added in version 3.14: トークンをコンテキストマネージャとして使
      用できるようになりました。

   reset(token)

      コンテキスト変数の値を、 *token* を生成した "ContextVar.set()"
      が呼び出される前の値にリセットします。

      例えば:

         var = ContextVar('var')

         token = var.set('new value')
         # 'var' を使うコード。
         # var.get() は 'new value' を返します。
         var.reset(token)

         # reset の呼び出し後は var の値が再びなくなるため、
         # var.get() を呼ぶと LookupError が送出されます。

      同じ *token* を 2 回使うことはできません。

class contextvars.Token

   *Token* オブジェクトは "ContextVar.set()" メソッドから返されます。
   これを "ContextVar.reset()" メソッドに渡すと、対応する *set* の呼び
   出し前の値に変数を戻せます。 1 つのトークンでコンテキスト変数をリセ
   ットできるのは 1 回だけです。

   トークンは コンテキストマネージャプロトコル をサポートし、コンテキ
   スト変数を自動的にリセットできます。 "ContextVar.set()" を参照して
   ください。

   トークンは、それを生成した "ContextVar" と同じ型について ジェネリッ
   ク です。

   Added in version 3.14: コンテキストマネージャとして使用できるように
   なりました。

   var

      読み出し専用のプロパティです。トークンを生成した "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

   "ContextVar" とその値を対応付けるマッピング。

   "Context()" は、値を持たない空のコンテキストを生成します。現在のコ
   ンテキストのコピーを得るには、"copy_context()" 関数を利用します。

   各スレッドには、実質的にそれぞれ独立した "Context" オブジェクトのス
   タックがあります。 *current context* は、現在のスレッドのスタックの
   最上部にある "Context" オブジェクトです。 スタック内のすべての
   "Context" オブジェクトは、*入っている* 状態と見なされます。

   コンテキストに *入る* には、その "run()" メソッドを呼び出します。
   これにより、そのコンテキストが現在のスレッドのコンテキストスタック
   の最上部に積まれ、現在のコンテキストになります。

   現在のコンテキストから *出る* には、"run()" メソッドに渡したコール
   バックから戻ります。 これにより、そのコンテキストがコンテキストスタ
   ックの最上部から取り除かれ、現在のコンテキストが、そのコンテキスト
   に入る前のものに戻ります。

   各スレッドが独自のコンテキストスタックを持つため、異なるスレッドで
   値を代入した場合、"ContextVar" オブジェクトは "threading.local()"
   と同様に振る舞います。

   ほかのスレッドで入っているコンテキストも含め、すでに入っているコン
   テキストに入ろうとすると、"RuntimeError" が送出されます。

   コンテキストから出た後は、どのスレッドからでも再び入ることができま
   す。

   "ContextVar.set()" メソッドによる "ContextVar" の値の変更は、現在の
   コンテキストに記録されます。 "ContextVar.get()" メソッドは、現在の
   コンテキストに対応する値を返します。 コンテキストから出ると、そのコ
   ンテキストに入っていた間に行ったコンテキスト変数の変更は、実質的に
   元に戻ります (必要であれば、そのコンテキストに再び入ることで変更後
   の値を復元できます)。

   Context は、 "collections.abc.Mapping" インターフェースを実装します
   。

   run(callable, *args, **kwargs)

      このコンテキストに入り、"callable(*args, **kwargs)" を実行してか
      ら、このコンテキストを出ます。 *callable* の戻り値を返すか、例外
      が発生した場合はそれを伝播させます。

      例:

         import contextvars

         var = contextvars.ContextVar('var')
         var.set('spam')
         print(var.get())  # 'spam'

         ctx = contextvars.copy_context()

         def main():
             # 'copy_context()' と 'ctx.run(main)' を呼ぶ前に
             # 'var' を 'spam' に設定したため、次のようになります:
             print(var.get())  # 'spam'
             print(ctx[var])  # 'spam'

             var.set('ham')

             # 'var' を 'ham' に設定した後は:
             print(var.get())  # 'ham'
             print(ctx[var])  # 'ham'

         # 'main' 関数が 'var' に行う変更はすべて
         # 'ctx' 内にとどまります。
         ctx.run(main)

         # 'main()' 関数は 'ctx' コンテキスト内で実行されたので、
         # 'var' の変更はその中にとどまります:
         print(ctx[var])  # 'ham'

         # 一方、'ctx' の外では 'var' は依然として 'spam' に設定されています:
         print(var.get())  # 'spam'

   copy()

      コンテキストオブジェクトの浅いコピーを返します。

   var in context

      *context* に *var* の値が設定されていた場合 "True" を返します;
      そうでない場合は "False" を返します。

   context[var]

      "ContextVar" *var* の値を返します。コンテキストオブジェクト内で
      変数が設定されていない場合は、"KeyError" を送出します。

   get(var[, default])

      *var* がコンテキストオブジェクトの中に値を持てば、その値を返しま
      す。さもなければ、*default* を返します。*default* を指定していな
      ければ、"None" を返します。

   iter(context)

      コンテキストオブジェクトに格納されている変数群のイテレータを返し
      ます。

   len(proxy)

      コンテキストオブジェクトに格納されている変数の数を返します。

   keys()

      コンテキストオブジェクト中のすべての変数のリストを返します。

   values()

      コンテキストオブジェクト中のすべての変数の値のリストを返します。

   items()

      コンテキストオブジェクト中のすべての変数について、変数とその値か
      らなる2要素のタプルのリストを返します。


asyncio サポート
================

コンテキスト変数は "asyncio" に標準でサポートされており、追加の設定な
しに使用できます。 例えば、以下はコンテキスト変数を使った単純な echo
サーバーです。 リモートクライアントのアドレスを、そのクライアントを扱
う Task 内で利用できるようにしています。

   import asyncio
   import contextvars

   client_addr_var = contextvars.ContextVar('client_addr')

   def render_goodbye():
       # 現在扱っているクライアントのアドレスを、この関数に
       # 明示的に渡さなくても参照できます。

       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)

       # ここから呼び出すどのコードでも、'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')  # ステータス行
       writer.write(b'\r\n')  # ヘッダー
       writer.write(render_goodbye())  # 本文
       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())

   # テストには telnet や curl を使えます:
   #     telnet 127.0.0.1 8081
   #     curl 127.0.0.1:8081
