with문 없이 파일/락을 다루다가 반복적으로 리소스가 새는 이유
문제 발생
파일을 열고 처리한 뒤 닫는 코드를 여러 함수에서 반복하다가, 예외가 발생하는 경로에서 close()가 호출되지 않아 파일 핸들이 누적되는 문제가 발생했습니다.
def process(path):
f = open(path)
data = f.read() # 여기서 예외가 나면 f.close()가 실행되지 않음
result = transform(data)
f.close()
return result원인 분석
open()으로 연 파일은 명시적으로 close()를 호출하기 전까지 운영체제 파일 디스크립터를 점유합니다. 정상 흐름에서만 close()를 호출하는 코드는, 그 사이에 예외가 발생하면 close()를 건너뛰고 함수가 종료됩니다. 이런 패턴이 여러 곳에 반복되면 파일 디스크립터가 서서히 고갈되어 결국 "Too many open files" 같은 오류로 이어집니다.
with문(컨텍스트 매니저)은 정확히 이 문제를 풀기 위한 언어 기능입니다 — 블록을 정상적으로 빠져나가든 예외로 빠져나가든 __exit__이 항상 호출되도록 보장합니다.
해결 방안
- 기본적으로 파일, 락, 네트워크 연결 등 "열고 닫아야 하는" 모든 리소스는
with문으로 다룹니다.
def process(path):
with open(path) as f:
data = f.read()
result = transform(data)
return result # 예외가 나도 파일은 반드시 닫힘- 직접 만든 리소스(커스텀 연결 객체 등)에 컨텍스트 매니저 프로토콜을 붙이고 싶다면 클래스에
__enter__/__exit__을 구현하거나, 더 간단하게는contextlib.contextmanager데코레이터로 제너레이터 함수 하나로 구현할 수 있습니다.
from contextlib import contextmanager
@contextmanager
def acquire_lock(lock):
lock.acquire()
try:
yield
finally:
lock.release() # 예외가 나도 반드시 실행
with acquire_lock(my_lock):
critical_section()yield 앞부분이 __enter__, yield 뒷부분(반드시 try/finally로 감싸야 함)이 __exit__ 역할을 합니다.
- 여러 리소스를 동시에 열어야 한다면
with open(a) as fa, open(b) as fb:처럼 하나의with문에 나열하거나,contextlib.ExitStack으로 개수가 동적으로 결정되는 경우까지 처리할 수 있습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.