Python 구조별: 컨텍스트 매니저와 'with' 문

발행: (2025년 12월 7일 오후 04:03 GMT+9)
5 min read
원문: Dev.to

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

락은 중요한 구역 내부에서 예외가 발생하더라도 자동으로 해제됩니다.

Back to Blog

관련 글

더 보기 »

중첩 리스트 평탄화

여러분, 안녕하세요! 👋 요즘 제가 좀 조용했죠. 사실 지난주에 꽤 심한 독감에 걸려서 완전히 쓰러졌어요. 🤒 그게 제가 …