Python 구조별: 컨텍스트 매니저와 'with' 문
Source: Dev.to
수동 파일 처리의 문제점
Timothy가 파일 처리 스크립트를 디버깅하던 중 Margaret는 로그에서 “too many open files” 오류를 발견했습니다.
def process_config():
f = open('config.txt', 'r', encoding='utf-8')
data = f.read()
# Process data...
if 'error' in data:
return None # Oops – file never closed!
f.close()
return data
조기 return이 실행되면 파일이 열려 있는 상태로 남게 됩니다. 가비지 컬렉션에 의존하는 것은 신뢰할 수 없으며 시스템 자원을 고갈시킬 수 있습니다.
전통적인 해결책: try/finally
def process_config():
f = open('config.txt', 'r', encoding='utf-8')
try:
data = f.read()
if 'error' in data:
return None
return data
finally:
f.close()
finally 블록은 함수가 어떻게 종료되든 f.close()가 실행되도록 보장하지만, 추가적인 보일러플레이트가 번거롭습니다.
with 문
Python의 with 문은 정리를 자동화합니다:
def process_config():
with open('config.txt', 'r', encoding='utf-8') as f:
data = f.read()
if 'error' in data:
return None
return data
# File is automatically closed here
with 블록은 명확한 진입점(파일 열기)과 종료점(들여쓰기 블록의 끝)을 가집니다. Python은 블록을 떠날 때 파일이 닫히도록 보장하는데, 이는 정상 실행이든 조기 return이든 예외가 발생했든 관계없이 적용됩니다.
with 작동 방식: 컨텍스트 매니저
with 문에서 사용되는 객체는 두 개의 특수 메서드를 구현해야 합니다:
__enter__(self): 블록 시작 시 호출되며, 반환값이as뒤의 변수에 바인딩됩니다.__exit__(self, exc_type, exc_val, exc_tb): 블록이 끝날 때 호출되며, 예외가 발생했을 경우 예외 정보를 받습니다.
간단한 컨텍스트 매니저 예시
class FileLogger:
def __init__(self, filename):
self.filename = filename
self.file = None
def __enter__(self):
print(f"Opening {self.filename}")
self.file = open(self.filename, 'w', encoding='utf-8')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Closing {self.filename}")
if self.file:
self.file.close()
return False # Do not suppress exceptions
# Usage
with FileLogger('output.log') as log:
log.write('Starting process\n')
log.write('Processing data\n')
# File automatically closed here
Output
Opening output.log
Closing output.log
__exit__에서의 예외 처리
with 블록 내부에서 예외가 발생하면 Python은 예외 세부 정보를 __exit__에 전달합니다. 이 메서드는 True를 반환함으로써 예외를 억제할지 여부를 결정할 수 있습니다.
class ErrorLogger:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Exception occurred: {exc_type.__name__}: {exc_val}")
return True # Suppress the exception
print("Exiting normally")
return False
with ErrorLogger():
print("Working...")
raise ValueError("Something went wrong!")
print("Continuing after exception")
Output
Entering context
Working...
Exception occurred: ValueError: Something went wrong!
Continuing after exception
여러 with 문 사용하기
with 문을 중첩하거나 한 줄에 결합할 수 있습니다(Python 3.1+).
# Nested
with open('input.txt', 'r', encoding='utf-8') as infile:
with open('output.txt', 'w', encoding='utf-8') as outfile:
outfile.write(infile.read())
# Combined
with open('input.txt', 'r', encoding='utf-8') as infile, \
open('output.txt', 'w', encoding='utf-8') as outfile:
outfile.write(infile.read())
두 파일 모두 획득 순서의 역순(마지막에 연 파일이 먼저)으로 닫히므로 안전합니다.
컨텍스트 매니저의 일반적인 사용 사례
- 파일 작업 – 자동으로 닫힘
- 데이터베이스 연결 – 자동으로 커밋/롤백
- 락 및 세마포어 – 자동으로 해제
- 네트워크 연결 – 자동으로 연결 해제
- 임시 상태 변경 – 자동으로 복원
- 정리가 필요한 모든 자원
예시: 스레드 락
import threading
lock = threading.Lock()
# Without context manager – risky
lock.acquire()
try:
# Critical section
pass
finally:
lock.release()
# With context manager – safe
with lock:
# Critical section
pass
락은 중요한 구역 내부에서 예외가 발생하더라도 자동으로 해제됩니다.