Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python contextlib 모듈: with 문으로 컨텍스트(리소스) 관리하기

Python contextlib 모듈이란?

파이썬 표준 라이브러리의 contextlib 모듈은 프로그램 내에서 리소스를 적절하게 관리하는 객체인 ContextManager(컨텍스트 매니저) 클래스를 정의합니다. 파이썬에는 이러한 컨텍스트 매니저와 함께 작동하는 with 키워드가 마련되어 있습니다.

대표적인 예가 파일 객체입니다. 내장 함수 open()이 반환하는 파일 객체는 ContextManager API를 지원하므로, 실제 개발 현장에서도 파일을 다룰 때 with 문을 자주 접하게 됩니다.

with 문 없이 파일을 다룰 때의 문제점

다음 코드는 파일을 열고 데이터를 기록한 뒤 직접 닫는 전통적인 방식입니다. 만약 작업 후 파일을 닫지 않으면 파일 디스크립터(file descriptor)가 누수되어 파일 손상으로 이어질 수 있습니다.

f = open("file.txt","w")
f.write("hello world")
f.close()

with 문을 활용한 안전한 파일 처리

같은 작업을 파일 객체의 컨텍스트 매니저 기능으로 수행하면 다음과 같습니다.

with open("file.txt","w") as f:
    f.write("hello world")
print("file is closed")

파일 객체는 ContextManager 프로토콜을 구현하고 있으며, with 키워드가 이를 활성화합니다. with 블록 안에는 해당 파일 객체에 대해 처리할 문장들이 들어갑니다. with 블록이 끝나는 순간 파일 객체는 자동으로 닫히기 때문에 close() 메서드를 명시적으로 호출할 필요가 없습니다. 즉, with 블록에 들어간 객체는 블록 내부에서만 유효하며, 블록이 종료되면 즉시 정리됩니다.

컨텍스트 매니저의 핵심 메서드: __enter__()와 __exit__()

ContextManager 클래스에는 두 가지 필수 메서드가 있습니다.

__enter__()with 블록이 시작될 때 호출됩니다. 프로그램이 해당 객체와 연관된 런타임 컨텍스트에 진입했음을 나타냅니다.

__exit__()with 블록이 끝날 때 호출됩니다. 프로그램이 해당 객체와 연관된 런타임 컨텍스트를 벗어났음을 나타냅니다.

파일 객체 역시 이 두 메서드를 가지고 있는데, 다음 인터프리터 세션으로 확인할 수 있습니다.

>>> f = open("file.txt","w")
>>> f.__enter__()
<_io.TextIOWrapper name='file.txt' mode='w' encoding='cp1252'>
>>> f.write("hello world")
11
>>> f.__exit__()
>>> f.write("hello world")
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    f.write("hello world")
ValueError: I/O operation on closed file.

__exit__() 메서드가 호출되면 파일이 닫힙니다. 따라서 이미 닫힌 파일에 데이터를 쓰려고 하면 위와 같이 ValueError가 발생하는 것입니다.

사용자 정의 컨텍스트 매니저 만들기

다음은 좀 더 일반적인 형태의 컨텍스트 매니저 활용 예입니다. 먼저 __enter__()__exit__() 메서드를 가진 클래스를 정의하고, 그 객체에 대해 with 문을 사용해 컨텍스트 매니저를 활성화합니다.

import contextlib

class WithExample:
    def __init__(self):
        print("object initialized")
    def __enter__(self):
        print("entered context")
    def __exit__(self, *args):
        print("exited context")

with WithExample() as w:
    print('this is a contextlib example')
    print('used by with statement')
print('end of with block')

실행 결과를 보면 with 블록이 시작되는 즉시 __enter__() 메서드가 실행되고, 블록 내부의 문장들이 처리된 뒤, 블록이 끝나면 __exit__() 메서드가 자동으로 호출됩니다.

object initialized
entered context
this is a contextlib example
used by with statement
exited context
end of with block

@contextmanager 데코레이터로 간결하게 구현하기

contextlib 모듈은 @contextmanager 데코레이터도 제공합니다. 이를 활용하면 제너레이터 기반 팩토리 함수를 작성하여 with 문을 자동으로 지원하도록 만들 수 있습니다. 다음은 데코레이터로 파일 객체의 컨텍스트 관리를 구현한 예입니다.

from contextlib import contextmanager

@contextmanager
def openfile(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

with openfile('file.txt') as f:
    f.write('hello world')
print('file is closed')

yield 이전까지의 코드는 컨텍스트 진입 시(__enter__에 해당), finally 블록의 코드는 컨텍스트 종료 시(__exit__에 해당) 실행됩니다. 덕분에 예외가 발생하더라도 파일이 반드시 닫히는 것을 보장할 수 있습니다.

마무리

이처럼 ContextManager는 파이썬에서 프로그램의 리소스를 효과적으로 관리할 수 있게 해주는 매우 유용한 기능입니다. 파일뿐 아니라 네트워크 연결, 데이터베이스 세션, 스레드 락 등 다양한 자원을 다룰 때 with 문과 contextlib을 함께 활용하면 코드의 안정성과 가독성을 크게 높일 수 있습니다.