본문 바로가기
Python

[Py] Context Manager: with statement!

by ds31x 2024. 11. 27.

1. Python의 Context Manager 개념

Python의 Context Manager
resource(자원, 리소스)를 안전하게 관리하기 위한 도구
(특정 메서드를 구현한 객체임).

 

일반적으로 file(파일), socket(네트워크 소켓), connection(데이터베이스 연결)과 같은 컴퓨터의 자원(resouce)를 사용할 때,

  • 시작(or open)
  • 종료(or close) 작업을 명시적으로 처리해야 함.

Context Manager를 사용하면 이를 간단하고 안전하게 처리할 수 있음.

 

Context Manager는

  • with statement와 함께 사용됨.
  • resource를 사용할 때, openclose 관련 정해진 작업들을 자동으로 수행할 수 있음.

2. Context Manager의 동작 원리

실제적으로 Context Manager는
다음의 특별한 메서드__enter____exit__를 구현한 object 임.

  1. __enter__:
    • with문에 진입할 때 호출되는 method.
    • resource를 초기화하거나 설정 작업을 수행.
    • __enter__ method의 반환값은 with statement의 as 키워드로 지정된 변수에 전달됨.
  2. __exit__:
    • with문을 벗어날 때 호출되는 method.
    • resource를 해제하여 OS에 돌려주는 작업을 수행.

2-1. __enter____exit__의 매개변수 및 반환값

2-1-1. __enter__:

Signature:

def __enter__(self):
  • 매개변수: 없음 (self 제외)
  • 반환값:
    • as 키워드로 지정할 resource handler 객체를 반환.
    • 보통 self를 반환하지만, 필요한 경우 다른 객체도 반환 가능.

2-1-2. __exit__:

Signature:

def __exit__(self, exc_type, exc_value, traceback):
  • 매개변수:
    • exc_type: 발생한 예외의 클래스 (예: ValueError)
    • exc_value: 예외 인스턴스
    • traceback: 예외의 트레이스백 객체
  • 반환값:
    • True: 예외를 무시하고 계속 실행
    • False: 예외를 호출한 곳으로 전달

2-2. Examples 1: 파일 관리**

Python의 내장 open 함수의 반환값은 Context Manager로 동작함.

with open("example.txt", "w") as f:
    f.write("Hello, world!")
# 파일은 자동으로 닫힘
  • open 함수의 Context Manager는
  • 파일을 열고(__enter__),
  • 작업이 끝난 후 파일을 닫음(__exit__).

3. 사용자 정의 Context Manager 만들기

직접 Context Manager를 만들려면 __enter____exit__ 메서드를 구현한 class를 정의하면 됨.


3-1. 예제 2: 사용자 정의 Context Manager

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting context...")
        if exc_type:
            print(f"An exception occurred: {exc_value}")
        return True  # 예외를 무시하려면 True를 반환, 아니면 False

# 사용
with MyContext() as ctx:
    print("Inside context")
    # raise ValueError("Something went wrong!")  # 예외 테스트
  • __exit__에서 False 를 반환하면 발생한 예외가 계속해서 전달됨: 이후에 명시적 처리 없으면 crash로 이어지게 된다.

출력:

Entering context...
Inside context
Exiting context...

3-2. 예제 3: 실제 리소스 관리 예

class ManagedFile:
    def __init__(self, filename):
        self.filename = filename
        self.file = None

    def __enter__(self):
        print(f"Opening file: {self.filename}")
        self.file = open(self.filename, "w")
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Closing file: {self.filename}")
        if self.file:
            self.file.close()

# 사용
with ManagedFile("test.txt") as f:
    f.write("Managed file example.")

 

출력:

Opening file: test.txt
Closing file: test.txt

 


4. Context Manager 에서의 예외 처리.

Context Manager 에서의 예외처리를 위해선 __exit__의 parameters를 이용하면 됨.

  • exc_type, exc_value, traceback__exit__ 메서드에게 전달되는 arguments가 할당되는 parameters로,
  • 이를 통해 발생한 예외 정보를 확인하거나 처리할 수 있음.

단순히 이를 출력하거나 기록(logging)하는 작업 외에 좀 더 복잡한 처리 작업을 구현 가능함.

 

일반적으로 예외처리로 수행되는 구현은 다음으로 나누어짐:

  1. 예외 단순 출력하거나 로깅(logging)에 사용
  2. 조건부 예외 무시
  3. 예외 재처리
  4. 트레이스백 분석

4-1. exc_type, exc_value, traceback의 역할

  1. exc_type: 발생한 예외의 클래스 (예: ValueError, TypeError 등).
  2. exc_value: 예외 인스턴스, 예외 객체 자체로 추가적인 정보(예: 에러 메시지)를 포함.
  3. traceback: 예외가 발생한 트레이스백 객체로, 예외가 발생한 코드의 호출 스택 정보를 포함.

4-2. 예제: 예외 단순 출력

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print(f"Exception type: {exc_type}")
            print(f"Exception value: {exc_value}")
            print(f"Traceback: {traceback}")
        print("Exiting context...")
        return False  # 예외를 재발생 시키려면 False 반환

# 테스트
with MyContext() as ctx:
    print("Inside context")
    raise ValueError("Something went wrong!")  # 일부러 예외 발생

출력:

Entering context...
Inside context
Exception type: <class 'ValueError'>
Exception value: Something went wrong!
Traceback: <traceback object at 0x7f1234567890>
Exiting context...
  • 이 경우, __exit__False를 반환했으므로 예외가 다시 호출된 곳으로 전달됨.

4-3. 예제: 예외 무시하기

예외를 무시하고 프로그램을 계속 실행하려면 __exit__에서 True를 반환.

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print(f"Exception occurred: {exc_value}, but it's ignored!")
        print("Exiting context...")
        return True  # 예외 무시

# 테스트
with MyContext() as ctx:
    print("Inside context")
    raise ValueError("This error is ignored!")
print("Continuing program...")

 

출력:

Entering context...
Inside context
Exception occurred: This error is ignored!, but it's ignored!
Exiting context...
Continuing program...

 


4-4. 예제: 예외에 따라 다른 처리 수행하기

특정 예외만 무시하고,
다른 예외는 다시 발생시키는 방식도 가능함.

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            if exc_type is ValueError:
                print(f"ValueError occurred: {exc_value}, but ignoring it.")
                return True  # ValueError 무시
            else:
                print(f"Other exception: {exc_value}. Propagating it.")
                return False  # 다른 예외는 재발생
        print("Exiting context without exceptions.")
        return True

# 테스트
try:
    with MyContext() as ctx:
        raise ValueError("Ignored error")
except Exception as e:
    print(f"Caught an exception: {e}")

try:
    with MyContext() as ctx:
        raise TypeError("Unhandled error")
except Exception as e:
    print(f"Caught an exception: {e}")

 

출력:

Entering context...
ValueError occurred: Ignored error, but ignoring it.
Exiting context without exceptions.
Entering context...
Other exception: Unhandled error. Propagating it.
Caught an exception: Unhandled error

4-5. traceback 활용하기

traceback 객체를 사용해 예외가 발생한 구체적인 위치를 추적할 수도 있음.

다음은 Python의 traceback 모듈을 사용하면 포맷팅된 트레이스백 정보를 출력하는 예제 코드임.

import traceback

class MyContext:
    def __enter__(self):
        print("Entering context...")
        return self

    def __exit__(self, exc_type, exc_value, traceback_obj):
        if exc_type:
            print("Exception details:")
            print(f"Type: {exc_type}")
            print(f"Value: {exc_value}")
            print("Traceback:")
            traceback.print_tb(traceback_obj)  # 트레이스백 출력
        print("Exiting context...")
        return False

# 테스트
with MyContext() as ctx:
    raise RuntimeError("Detailed traceback example")

 

출력:

Entering context...
Exception details:
Type: <class 'RuntimeError'>
Value: Detailed traceback example
Traceback:
  File "example.py", line 25, in <module>
    raise RuntimeError("Detailed traceback example")
Exiting context...