
파이썬의 with 구문은 파일이나 네트워크 연결과 같이 사용이 끝난 후 반드시 정리해야 하는 자원들을 효율적이고 안전하게 다루기 위한 구문입니다. 이는 try...finally 블록을 사용하는 것에 비해 코드를 훨씬 간결하게 만들며, 오류가 발생하더라도 자원 해제를 보장해줍니다.
with 구문의 원리
with 구문의 핵심은 바로 컨텍스트 매니저입니다. 컨텍스트 매니저는 두 가지 특별한 메서드, 즉 __enter__()와 __exit__()를 포함하는 객체입니다.
__enter__(): 이 메서드는with블록이 시작될 때 호출됩니다. 주로 자원을 획득하거나 준비하는 역할을 하며, 이 메서드가 반환하는 값은as키워드 뒤에 오는 변수에 할당됩니다.__exit__(): 이 메서드는with블록이 정상적으로 완료되거나 예외가 발생했을 때 호출됩니다. 자원을 정리하고 해제하는 역할을 수행합니다.
이러한 구조 덕분에 개발자는 자원 해제에 대한 걱정 없이 with 블록 내부의 핵심 작업에만 집중할 수 있습니다.
with 구문 사용 예시
일반적으로 파일을 다룰 때 with 구문이 어떻게 유용하게 쓰이는지 아래 예시를 통해 확인할 수 있습니다.
일반적인 파일 처리
file = open("sample.txt", "w")
try:
file.write("Hello, Python!")
finally:
file.close() # 파일을 닫는 것을 보장
위 코드는 try...finally를 사용해 파일을 연 후, 작업이 끝나면 반드시 닫도록 보장합니다. 하지만 이 방법은 코드가 다소 복잡해 보일 수 있습니다.
with 구문을 사용한 파일 처리
with open("sample.txt", "w") as f:
f.write("Hello, Python!")
# with 블록을 벗어나는 순간 f.close()가 자동으로 호출됩니다.
with 구문을 사용하면 코드가 훨씬 간결해지며, with 블록을 벗어나는 순간 파일이 자동으로 닫히므로 자원 해제를 명시적으로 호출할 필요가 없어집니다.
나만의 컨텍스트 매니저 만들기
파일 처리뿐만 아니라, 직접 정의한 클래스나 함수를 with 구문과 함께 사용하고 싶다면 컨텍스트 매니저를 직접 만들 수 있습니다. 여기에는 두 가지 방법이 있습니다.
1. 클래스를 이용하는 방법
__enter__()와 __exit__() 메서드를 구현하는 클래스를 정의하는 방식입니다. 이는 컨텍스트 매니저를 만드는 가장 기본적인 방법입니다.
class CustomManager:
def __init__(self, name):
self.name = name
print(f"[{self.name}] 컨텍스트 매니저 초기화")
def __enter__(self):
print(f"[{self.name}] 자원 획득: __enter__ 호출")
return f"리소스 '{self.name}'"
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"[{self.name}] 자원 해제: __exit__ 호출")
if exc_type:
print(f"[{self.name}] 예외 발생: {exc_val}")
print(f"[{self.name}] 컨텍스트 종료")
return False # True를 반환하면 예외가 억제됨
사용 예시:
with CustomManager("Sample Resource") as res:
print(f"with 블록 내부, 획득한 값: '{res}'")
print("with 블록 외부")
실행 결과:
[Sample Resource] 컨텍스트 매니저 초기화
[Sample Resource] 자원 획득: __enter__ 호출
with 블록 내부, 획득한 값: '리소스 'Sample Resource''
[Sample Resource] 자원 해제: __exit__ 호출
[Sample Resource] 컨텍스트 종료
with 블록 외부
2. @contextlib.contextmanager 데코레이터 활용
contextlib 모듈의 @contextmanager 데코레이터를 사용하면 yield 키워드를 활용해 클래스를 정의하지 않고도 함수 형태로 간단하게 컨텍스트 매니저를 만들 수 있습니다.
from contextlib import contextmanager
@contextmanager
def simple_manager(name):
print(f"[{name}] with 시작 (자원 획득)")
try:
yield f"함수형 리소스 '{name}'"
finally:
print(f"[{name}] with 종료 (자원 해제)")
사용 예시:
with simple_manager("Simple Test") as simple_res:
print(f"with 블록 내, 획득한 자원: '{simple_res}'")
실행 결과:
[Simple Test] with 시작 (자원 획득)
with 블록 내, 획득한 자원: '함수형 리소스 'Simple Test''
[Simple Test] with 종료 (자원 해제)
이처럼 with 구문과 컨텍스트 매니저를 활용하면 복잡한 자원 관리 로직을 깔끔하고 안전하게 처리할 수 있습니다.