15. 콘텍스트 관리자와 else 블록

 

Remarks

이 글은 전문가를 위한 파이썬(Fluent Python)을 정리한 자료입니다.


1. Context manager

  1. __enter__(), __exit()__을 구현하여 context manager를 생성할 수 있다.
     import traceback as tb
     from types import TracebackType
    
     class ExceptionHandler:
         def __init__(self, exit_return_value):
             self.exit_return_value = exit_return_value
            
         def __enter__(self):
             return self
            
         def __exit__(self, exc_type: type | None, exc_value: Exception | None, traceback: TracebackType | None):
             if exc_type is ZeroDivisionError:
                 print(tb.format_exc())
                 return self.exit_return_value
    
  2. __exit__()은 exception에 대한 정보를 입력값으로 받는다.
    • exc_type
      • 예외 발생 X: None
      • 예외 발생 O: Exception type
    • exc_value
      • 예외 발생 X: None
      • 예외 발생 O: Exception
    • traceback
      • 예외 발생 X: None
      • 예외 발생 O: traceback 객체. 이것보다 traceback 패키지 임포트해서 traceback.format_exc() 사용하는 게 낫다.
  3. __exit__()의 return값이 True 이외의 값을 반환하면 with 블록에서 발생한 예외가 상위 코드로 전달된다.
    True를 반환해서 예외가 처리되었음을 파이썬 인터프리터에 알려주자.
     with ExceptionHandler(True):
         1/0
    
     Traceback (most recent call last):
     File "/tmp/ipykernel_1487515/3039134459.py", line 2, in <module>
         1/0
         ~^~
     ZeroDivisionError: division by zero
    
     with ExceptionHandler(False):
         1/0
    
     Traceback (most recent call last):
     File "/tmp/ipykernel_1487515/828479559.py", line 2, in <module>
         1/0
         ~^~
     ZeroDivisionError: division by zero
     ---------------------------------------------------------------------------
     ZeroDivisionError                         Traceback (most recent call last)
     Cell In[92], line 2
         1 with ExceptionHandler(False):
     ----> 2     1/0
    
     ZeroDivisionError: division by zero
    

2. @contextmanager

  1. class로 context manager를 생성하는 대신, decorator를 이용하면 간단하게 구현이 가능하다.
     import contextlib
    
     @contextlib.contextmanager
     def looking_glass():
         import sys
         original_write = sys.stdout.write
    
         def reverse_write(text):
             original_write(text[::-1])
    
         sys.stdout.write = reverse_write
         msg = ""
         try:
             yield 'JABBERWOCKY'
         except Exception as e:
             msg = f"Unexpected exception occurs: {e}"
         finally:
             sys.stdout.write = original_write
             if msg:
                 print(msg)
    
    1. yield 문 앞에 있는 코드는 __enter__()를 호출할 때 실행되고, yield문 뒤에 있는 코드는 __exit()__이 호출될 때 실행된다.
    2. with 블록 안에서 예외가 발생하면 시스템이 불안정한 상태로 남게되는 것을 방지하기 위해 예외처리가 필요하다.