"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" 객체를 반환합니다.

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

class contextvars.Token

   *Token* 객체는 "ContextVar.set()" 메서드에 의해 반환됩니다.
   "ContextVar.reset()" 메서드에 전달해서 변수의 값을 해당 *set* 이전
   의 값으로 되돌릴 수 있습니다.

   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()" 함수를 사용하십시오.

   Every thread will have a different top-level "Context" object. This
   means that a "ContextVar" object behaves in a similar fashion to
   "threading.local()" when values are assigned in different threads.

   Context는 "collections.abc.Mapping" 인터페이스를 구현합니다.

   run(callable, *args, **kwargs)

      Execute "callable(*args, **kwargs)" code in the context object
      the *run* method is called on.  Return the result of the
      execution or propagate an exception if one occurred.

      Any changes to any context variables that *callable* makes will
      be contained in the context object:

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

         def main():
             # 'var' was set to 'spam' before
             # calling 'copy_context()' and 'ctx.run(main)', so:
             # var.get() == ctx[var] == 'spam'

             var.set('ham')

             # Now, after setting 'var' to 'ham':
             # var.get() == ctx[var] == 'ham'

         ctx = copy_context()

         # Any changes that the 'main' function makes to 'var'
         # will be contained in 'ctx'.
         ctx.run(main)

         # The 'main()' function was run in the 'ctx' context,
         # so changes to 'var' are contained in it:
         # ctx[var] == 'ham'

         # However, outside of 'ctx', 'var' is still set to 'spam':
         # var.get() == 'spam'

      The method raises a "RuntimeError" when called on the same
      context object from more than one OS thread, or when called
      recursively.

   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():
       # 현재 처리중인 클라이언트의 주소를 이 함수에 명시 적으로 전달하지 않고도
       # 액세스 할 수 있습니다.

       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')  # 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())

   # 테스트하려면 telnet이나 curl을 사용할 수 있습니다:
   #     telnet 127.0.0.1 8081
   #     curl 127.0.0.1:8081
