"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" 객체는 컨텍스트 변수에 대
   해 강한 참조를 유지해서 컨텍스트 변수가 제대로 가비지 수집되지 못하
   게 합니다.

   name

      변수의 이름. 읽기 전용 프로퍼티입니다.

      Added in version 3.7.1.

   get([default])

      현재 컨텍스트의 컨텍스트 변수에 대한 값을 반환합니다.

      현재 컨텍스트에서 변수에 대한 값이 없는 경우 메서드는:

      * 제공된 경우 메서드의 *default* 인자 값을 반환합니다; 또는

      * 생성 시에 제공된 경우, 컨텍스트 변수의 기본값을 반환합니다; 또
        는

      * "LookupError" 를 발생시킵니다.

   set(value)

      현재 컨텍스트에서 컨텍스트 변수의 새 값을 설정하려면 호출합니다.

      필수 *value* 인자는 컨텍스트 변수의 새 값입니다.

      "ContextVar.reset()" 메서드를 통해 변수를 이전 값으로 복원하는
      데 사용할 수 있는 "Token" 객체를 반환합니다.

      For convenience, the token object can be used as a context
      manager to avoid calling "ContextVar.reset()" manually:

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

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

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

      It is a shorthand for:

         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: Added support for using tokens as context
      managers.

   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를 발생시킵니다.

      The same *token* cannot be used twice.

class contextvars.Token

   *Token* objects are returned by the "ContextVar.set()" method. They
   can be passed to the "ContextVar.reset()" method to revert the
   value of the variable to what it was before the corresponding
   *set*. A single token cannot reset a context variable more than
   once.

   Tokens support the context manager protocol to automatically reset
   context variables. See "ContextVar.set()".

   Added in version 3.14: Added support for usage as a context
   manager.

   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

   "ContextVars" 에서 그 값으로의 매핑.

   "Context()" 는 값이 없는 빈 컨텍스트를 만듭니다. 현재 컨텍스트의 복
   사본을 얻으려면 "copy_context()" 함수를 사용하십시오.

   Each thread has its own effective stack of "Context" objects.  The
   *current context* is the "Context" object at the top of the current
   thread's stack.  All "Context" objects in the stacks are considered
   to be *entered*.

   *Entering* a context, which can be done by calling its "run()"
   method, makes the context the current context by pushing it onto
   the top of the current thread's context stack.

   *Exiting* from the current context, which can be done by returning
   from the callback passed to the "run()" method, restores the
   current context to what it was before the context was entered by
   popping the context off the top of the context stack.

   Since each thread has its own context stack, "ContextVar" objects
   behave in a similar fashion to "threading.local()" when values are
   assigned in different threads.

   Attempting to enter an already entered context, including contexts
   entered in other threads, raises a "RuntimeError".

   After exiting a context, it can later be re-entered (from any
   thread).

   Any changes to "ContextVar" values via the "ContextVar.set()"
   method are recorded in the current context.  The "ContextVar.get()"
   method returns the value associated with the current context.
   Exiting a context effectively reverts any changes made to context
   variables while the context was entered (if needed, the values can
   be restored by re-entering the context).

   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():
             # 'var' 는 'copy_context()' 와 'ctx.run(main)' 을
             # 호출하기 전에 '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' 에 대한 변경은 'ctx' 에 포함되어 있습니다:
         print(ctx[var])  # 'ham'

         # 그러나, 'ctx' 외부에서, 'var' 는 여전히 'spam' 으로 설정되어있습니다:
         print(var.get())  # 'spam'

   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, it is now possible to get the
       # 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
