본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2026. 3. 6. · 4 Views
파일 입출력에서 with문 사용하기 완벽 가이드
Python에서 파일을 안전하게 다루는 with문의 모든 것을 알아봅니다. 파일 자동 닫기부터 예외 처리까지, 실무에서 반드시 알아야 할 핵심 개념을 이북처럼 술술 읽히는 스타일로 정리했습니다.
목차
- 왜 with문이 필요할까요
- with문의 동작 원리 이해하기
- contextlib로 더 쉽게 만들기
- 중첩 with문과 다중 파일 처리
- with문으로 임시 파일 안전하게 다루기
- with문과 함께 쓰는 유용한 패턴들
1. 왜 with문이 필요할까요
김개발 씨가 로그 분석 프로그램을 만들던 중이었습니다. 파일을 열어서 데이터를 읽고 처리하는 기능을 구현했는데, 어느 순간부터 서버의 파일 디스크립터가 부족하다는 에러가 뜨기 시작했습니다.
박시니어 씨가 코드를 보더니 한숨을 쉬었습니다.
파일을 열고 나서 닫지 않으면 시스템 자원이 계속 점유됩니다. 마치 도서관에서 책을 빌리고 반납하지 않는 것과 같습니다.
with문은 파일 사용이 끝나면 자동으로 닫아주는 편리한 기능입니다.
다음 코드를 살펴봅시다.
# 잘못된 예: 파일을 닫지 않음
def read_log_bad(filename):
file = open(filename, 'r')
content = file.read()
# file.close()를 깜빡함!
return content
# 올바른 예: with문 사용
def read_log_good(filename):
with open(filename, 'r') as file:
content = file.read()
# 블록을 벗어나면 자동으로 file.close() 호출
return content
# 여러 파일을 열 때도 with문이 유용
with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
data = infile.read()
outfile.write(data.upper())
김개발 씨는 입사 후 처음으로 로그 분석 프로젝트를 맡게 되었습니다. 수백 메가바이트짜리 로그 파일들을 읽어서 통계를 내는 업무였습니다.
처음에는 순조로웠습니다. open으로 파일을 열고 read로 내용을 읽고 처리했습니다.
그런데 며칠 후, 서버에서 이상한 에러가 발생하기 시작했습니다. "Too many open files"라는 생소한 메시지였습니다.
박시니어 씨가 다가와 코드를 살펴보았습니다. "아, 여기가 문제네요.
파일을 열고 닫지 않았어요." 김개발 씨는 고개를 갸웃했습니다. 파일을 열면 자동으로 닫히는 게 아니었나요?
아니었습니다. Python에서 open으로 파일을 열면 명시적으로 close를 호출해야만 파일이 닫힙니다.
이를 깜빡하면 파일 디스크립터가 계속 열린 상태로 남게 됩니다. 쉽게 비유하자면, 도서관에서 책을 빌리고 반납하지 않는 것과 같습니다.
책을 계속 빌리기만 하고 반납하지 않으면 언젠가 도서관에 책이 부족해지겠죠. 파일도 마찬가지입니다.
시스템에서 동시에 열 수 있는 파일 개수에는 한계가 있습니다. 리눅스 시스템에서는 보통 1024개 정도가 기본값입니다.
close를 호출하지 않으면 이 한계에 도달했을 때 더 이상 파일을 열 수 없게 됩니다. 더 큰 문제는 예외 상황입니다.
파일을 읽는 도중에 에러가 발생하면 어떻게 될까요? ```python file = open('data.txt', 'r') data = file.read() # 여기서 에러 발생!
file.close() # 실행되지 않음 ``` 에러가 발생하면 close 라인이 실행되지 않습니다. 파일이 열린 채로 방치되는 것입니다.
이런 문제를 해결하기 위해 전통적으로 try-finally 구문을 사용했습니다. python file = open('data.txt', 'r') try: data = file.read() finally: file.close() # 에러가 나도 무조건 실행 하지만 이 코드는 번거롭습니다.
매번 try와 finally를 작성해야 하니까요. 코드도 길어지고 가독성도 떨어집니다.
바로 이런 불편함을 해결하기 위해 Python 2.5부터 with문이 도입되었습니다. with문을 사용하면 들여쓰기 블록이 끝날 때 자동으로 close가 호출됩니다.
python with open('data.txt', 'r') as file: data = file.read() # 여기서 자동으로 file.close() 호출 훨씬 깔끔해졌습니다. 에러가 발생해도 무조건 파일이 닫힙니다.
개발자는 close를 신경 쓰지 않고 비즈니스 로직에만 집중할 수 있습니다. 실무에서는 파일뿐만 아니라 데이터베이스 연결, 네트워크 소켓, 락 등 다양한 자원에서 with문을 활용합니다.
모두 "사용 후 정리"가 필요한 자원들이죠. 김개발 씨는 모든 파일 열기 코드를 with문으로 수정했습니다.
그 후로는 "Too many open files" 에러가 다시는 발생하지 않았습니다.
실전 팁
💡 - with문은 파일뿐 아니라 데이터베이스 연결, 네트워크 소켓 등에도 사용하세요
- 여러 파일을 열 때는 쉼표로 구분하여 한 with문에 작성할 수 있습니다
2. with문의 동작 원리 이해하기
김개발 씨는 with문이 파일을 자동으로 닫아준다는 건 알겠는데, 도대체 어떻게 그게 가능한지 궁금했습니다. 마법처럼 동작하는 건가요?
박시니어 씨가 커피를 마시며 설명해 주었습니다.
with문은 컨텍스트 매니저라는 특별한 객체와 함께 동작합니다. 컨텍스트 매니저는 진입과 진출 시점에 특정 작업을 수행하는 객체입니다.
마치 호텔 체크인과 체크아웃처럼 말이죠.
다음 코드를 살펴봅시다.
# 컨텍스트 매니저의 내부 구조 이해하기
class MyFileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
# with문 블록 진입 시 호출
print(f"파일 {self.filename}을 엽니다")
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
# with문 블록 종료 시 호출 (에러가 나도 호출됨)
print(f"파일 {self.filename}을 닫습니다")
if self.file:
self.file.close()
return False # 예외를 전파함
# 직접 만든 컨텍스트 매니저 사용
with MyFileManager('test.txt', 'w') as f:
f.write('Hello, Context Manager!')
# 여기서 __exit__가 자동 호출됨
김개발 씨는 with문이 마법처럼 파일을 닫아주는 게 신기했습니다. 어떤 원리로 이런 일이 가능한 걸까요?
박시니어 씨가 화이트보드에 다이어그램을 그리며 설명을 시작했습니다. "with문은 사실 컨텍스트 매니저라는 특별한 프로토콜을 사용합니다." 컨텍스트 매니저.
생소한 용어였습니다. 하지만 원리를 알고 나면 어렵지 않습니다.
쉽게 비유하자면, 호텔 투숙과 비슷합니다. 호텔에 체크인하면 프론트 데스크가 방 열쇠를 주고, 체크아웃하면 방을 정리하고 열쇠를 회수합니다.
손님은 그냥 방을 사용하기만 하면 됩니다. 컨텍스트 매니저도 똑같습니다.
enter 메서드가 체크인을 담당하고, exit 메서드가 체크아웃을 담당합니다. python with open('file.txt', 'r') as f: # __enter__가 호출되어 파일이 열림 data = f.read() # __exit__가 호출되어 파일이 닫힘 위 코드는 실제로 다음과 같이 동작합니다.
python manager = open('file.txt', 'r') f = manager.__enter__() # 파일 열기 try: data = f.read() finally: manager.__exit__() # 파일 닫기 open 함수가 반환하는 파일 객체는 이미 __enter__와 exit 메서드를 구현하고 있습니다. 그래서 with문과 함께 사용할 수 있는 것입니다.
김개발 씨가 궁금해했습니다. "그럼 제가 직접 컨텍스트 매니저를 만들 수도 있나요?" 물론입니다.
__enter__와 exit 메서드만 구현하면 어떤 클래스든 컨텍스트 매니저가 됩니다. 예를 들어, 실행 시간을 측정하는 컨텍스트 매니저를 만들어봅시다.
python import time class Timer: def __enter__(self): self.start = time.time() return self def __exit__(self, *args): self.end = time.time() print(f"실행 시간: {self.end - self.start:.2f}초") with Timer(): # 시간이 걸리는 작업 time.sleep(1) # "실행 시간: 1.00초" 출력 이렇게 하면 어떤 코드든 with Timer() 블록으로 감싸면 실행 시간을 자동으로 측정할 수 있습니다. exit 메서드의 매개변수를 눈여겨보세요.
exc_type, exc_val, exc_tb 세 개의 인자를 받습니다. 이것은 블록 내부에서 예외가 발생했을 때 예외 정보를 담고 있습니다.
python def __exit__(self, exc_type, exc_val, exc_tb): if exc_type: print(f"예외 발생: {exc_val}") self.file.close() return False # True면 예외 무시 __exit__가 True를 반환하면 예외가 무시됩니다. False를 반환하면 예외가 계속 전파됩니다.
대부분의 경우 False를 반환하는 것이 안전합니다. 김개발 씨는 이제 with문의 원리를 확실히 이해했습니다.
마법이 아니라 잘 설계된 프로토콜이었던 것입니다. 실무에서는 이미 많은 라이브러리가 컨텍스트 매니저를 제공합니다.
데이터베이스 연결, HTTP 세션, 스레드 락 등등. 이들을 with문과 함께 사용하면 자원 정리를 안전하게 처리할 수 있습니다.
실전 팁
💡 - __exit__의 반환값을 True로 하면 예외를 무시할 수 있습니다
- 커스텀 컨텍스트 매니저로 로깅, 타이머, 락 등을 구현할 수 있습니다
3. contextlib로 더 쉽게 만들기
김개발 씨가 커스텀 컨텍스트 매니저를 만들어 보고 싶었습니다. 하지만 클래스를 만들고 __enter__와 __exit__를 구현하는 게 조금 번거로워 보였습니다.
박시니어 씨가 더 간단한 방법을 알려주었습니다.
Python의 contextlib 모듈은 컨텍스트 매니저를 더 쉽게 만들 수 있는 도구들을 제공합니다. 특히 @contextmanager 데코레이터를 사용하면 함수 하나로 컨텍스트 매니저를 만들 수 있습니다.
다음 코드를 살펴봅시다.
from contextlib import contextmanager
@contextmanager
def open_file(filename, mode):
"""파일을 열고 자동으로 닫는 컨텍스트 매니저"""
f = open(filename, mode)
try:
yield f # __enter__ 역할: 파일 객체 반환
finally:
f.close() # __exit__ 역할: 파일 닫기
# 사용 예시
with open_file('example.txt', 'w') as f:
f.write('contextlib is awesome!')
# 데이터베이스 연결 예시
@contextmanager
def db_connection(host):
print(f"{host}에 연결 중...")
conn = {"host": host, "connected": True} # 가짜 DB 연결
try:
yield conn
finally:
print("연결 종료")
conn["connected"] = False
with db_connection("localhost") as db:
print(f"연결 상태: {db['connected']}")
김개발 씨는 커스텀 컨텍스트 매니저를 만들어 보려고 클래스를 작성했습니다. init, enter, exit 메서드를 모두 구현해야 했습니다.
코드가 꽤 길어졌습니다. 박시니어 씨가 지나가다 말했습니다.
"contextlib 써보셨어요?" contextlib. 처음 듣는 모듈이었습니다.
Python 표준 라이브러리에 포함된 모듈로, 컨텍스트 매니저를 만드는 작업을 크게 단순화해 줍니다. 가장 자주 사용하는 것은 @contextmanager 데코레이터입니다.
이 데코레이터를 함수에 붙이면, 그 함수가 컨텍스트 매니저로 변신합니다. 핵심은 yield 키워드입니다.
yield 이전의 코드는 enter 역할을 하고, yield 이후의 코드는 exit 역할을 합니다. python from contextlib import contextmanager @contextmanager def my_context(): # __enter__ 부분 print("진입") resource = create_resource() yield resource # 여기서 with문 블록으로 제어 이동 # __exit__ 부분 print("진출") cleanup_resource(resource) 김개발 씨는 눈이 커졌습니다.
"이게 다예요? 클래스 안 만들어도 돼요?" 네, 이게 다입니다.
훨씬 간단합니다. 실제로 파일을 다루는 컨텍스트 매니저를 만들어 봅시다.
python @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() try-finally가 보이시나요? yield에서 예외가 발생할 수 있기 때문에 finally로 반드시 정리 작업을 해야 합니다.
이것이 중요합니다. 김개발 씨가 실무 예제를 요청했습니다.
데이터베이스 연결을 관리하는 컨텍스트 매니저를 만들어 보겠습니다. python import sqlite3 from contextlib import contextmanager @contextmanager def db_transaction(db_path): conn = sqlite3.connect(db_path) try: yield conn conn.commit() # 성공 시 커밋 except Exception: conn.rollback() # 실패 시 롤백 raise finally: conn.close() with db_transaction('myapp.db') as conn: cursor = conn.cursor() cursor.execute("INSERT INTO users VALUES (1, 'Kim')") 이 컨텍스트 매니저는 트랜잭션까지 관리합니다.
블록 내부에서 예외가 발생하면 자동으로 롤백하고, 성공하면 커밋합니다. 그리고 무조건 연결을 닫습니다.
contextlib에는 더 유용한 도구들이 있습니다. closing, suppress, redirect_stdout 등등.
python from contextlib import suppress, redirect_stdout import io # 특정 예외 무시 with suppress(FileNotFoundError): os.remove('maybe_exists.txt') # 표준 출력 리다이렉트 f = io.StringIO() with redirect_stdout(f): print("이 내용은 파일로") output = f.getvalue() 김개발 씨는 contextlib의 편리함에 감탄했습니다. 클래스를 만들지 않아도 되니 코드가 훨씬 간결해졌습니다.
박시니어 씨가 덧붙였습니다. "contextlib는 성능이 중요한 곳보다는 편의성이 중요한 곳에 적합해요.
아주 빈번하게 호출되는 코드라면 클래스 기반 컨텍스트 매니저가 더 나을 수 있어요."
실전 팁
💡 - yield 다음에는 반드시 try-finally로 정리 코드를 감싸야 합니다
- suppress는 특정 예외를 조용히 무시할 때 편리합니다
4. 중첩 with문과 다중 파일 처리
김개발 씨가 데이터 마이그레이션 작업을 하던 중이었습니다. 여러 파일을 동시에 열어서 읽고 쓰는 작업이 필요했습니다.
파일을 세 개나 열어야 하는데, with문을 어떻게 써야 할지 막막했습니다.
여러 파일을 동시에 다뤄야 할 때는 중첩 with문이나 한 줄 with문을 사용할 수 있습니다. 파일 간의 의존 관계에 따라 적절한 방법을 선택해야 합니다.
다음 코드를 살펴봅시다.
# 방법 1: 중첩 with문 (들여쓰기 증가)
with open('file1.txt', 'r') as f1:
with open('file2.txt', 'r') as f2:
with open('output.txt', 'w') as out:
out.write(f1.read() + f2.read())
# 방법 2: 한 줄에 여러 파일 열기 (Python 3.1+)
with open('file1.txt', 'r') as f1, \
open('file2.txt', 'r') as f2, \
open('output.txt', 'w') as out:
out.write(f1.read() + f2.read())
# 방법 3: ExitStack으로 동적 파일 관리
from contextlib import ExitStack
filenames = ['file1.txt', 'file2.txt', 'file3.txt']
with ExitStack() as stack:
files = [stack.enter_context(open(f, 'r')) for f in filenames]
# 모든 파일을 리스트로 관리
contents = [f.read() for f in files]
# 실무 예제: CSV 파일 병합
import csv
from contextlib import ExitStack
def merge_csv_files(output_file, input_files):
with ExitStack() as stack:
# 모든 입력 파일 열기
readers = [
csv.reader(stack.enter_context(open(f, 'r')))
for f in input_files
]
# 출력 파일 열기
writer = csv.writer(stack.enter_context(
open(output_file, 'w', newline='')
))
# 모든 데이터 병합
for reader in readers:
for row in reader:
writer.writerow(row)
김개발 씨는 데이터 마이그레이션 프로젝트를 진행 중이었습니다. 여러 개의 로그 파일을 읽어서 하나로 합치는 작업이었습니다.
파일이 두 개라면 간단했습니다. python with open('log1.txt', 'r') as f1, open('log2.txt', 'r') as f2: # 처리 하지만 파일이 세 개, 네 개로 늘어나면 어떨까요?
python with open('log1.txt', 'r') as f1: with open('log2.txt', 'r') as f2: with open('log3.txt', 'r') as f3: with open('output.txt', 'w') as out: # 들여쓰기 지옥! 들여쓰기가 깊어지고 코드가 읽기 어려워집니다.
이를 "들여쓰기 지옥"이라고 부릅니다. Python 3.1부터는 한 with문에 여러 파일을 열 수 있습니다.
python with open('log1.txt', 'r') as f1, \ open('log2.txt', 'r') as f2, \ open('log3.txt', 'r') as f3, \ open('output.txt', 'w') as out: # 깔끔! 훨씬 깔끔해졌습니다.
백슬래시로 줄을 나누면 가독성도 좋습니다. 하지만 김개발 씨에게 더 큰 문제가 있었습니다.
처리해야 할 파일 개수가 고정되어 있지 않았습니다. 사용자가 입력한 만큼 파일을 처리해야 했습니다.
이럴 때는 ExitStack이 정답입니다. python from contextlib import ExitStack filenames = get_user_input_files() # 개수가 정해지지 않음 with ExitStack() as stack: files = [stack.enter_context(open(f, 'r')) for f in filenames] # 이제 files 리스트로 모든 파일에 접근 for f in files: process(f.read()) ExitStack은 컨텍스트 매니저들을 동적으로 관리합니다.
enter_context로 추가된 모든 매니저는 블록이 끝날 때 자동으로 정리됩니다. 김개발 씨가 실제로 사용한 코드를 봅시다.
python from contextlib import ExitStack def merge_logs(output_path, input_paths): with ExitStack() as stack: # 모든 입력 파일 열기 input_files = [ stack.enter_context(open(p, 'r')) for p in input_paths ] # 출력 파일 열기 output_file = stack.enter_context( open(output_path, 'w') ) # 순차적으로 읽어서 쓰기 for i, infile in enumerate(input_files): output_file.write(f"=== File {i+1} ===\n") output_file.write(infile.read()) output_file.write("\n") 이 코드는 몇 개의 파일이 들어오든 상관없이 안전하게 동작합니다. 모든 파일은 블록이 끝날 때 자동으로 닫힙니다.
ExitStack의 또 다른 장점은 에러 처리입니다. 파일 여는 도중 에러가 나도 이미 열린 파일들은 모두 안전하게 닫힙니다.
박시니어 씨가 덧붙였습니다. "ExitStack은 파일뿐 아니라 모든 컨텍스트 매니저에 사용할 수 있어요.
데이터베이스 연결 여러 개, 락 여러 개 등등." 김개발 씨는 이제 어떤 상황에서도 with문을 자신 있게 사용할 수 있게 되었습니다.
실전 팁
💡 - 파일 2-3개면 쉼표 구분 방식이 가장 깔끔합니다
- 파일 개수가 동적이면 ExitStack을 사용하세요
5. with문으로 임시 파일 안전하게 다루기
김개발 씨가 테스트 코드를 작성하던 중이었습니다. 임시 파일을 만들어서 테스트하고 삭제해야 했습니다.
하지만 테스트가 실패하면 임시 파일이 남아있는 문제가 있었습니다.
tempfile 모듈과 with문을 조합하면 임시 파일을 안전하게 관리할 수 있습니다. 프로그램이 종료되면 자동으로 삭제되므로 디스크 공간을 낭비하지 않습니다.
다음 코드를 살펴봅시다.
import tempfile
import os
# 방법 1: NamedTemporaryFile - 자동 삭제되는 임시 파일
with tempfile.NamedTemporaryFile(mode='w', delete=True) as f:
f.write('임시 데이터')
f.flush()
temp_path = f.name
print(f"임시 파일 위치: {temp_path}")
# 블록 종료 시 자동 삭제
# 방법 2: TemporaryDirectory - 임시 폴더
with tempfile.TemporaryDirectory() as temp_dir:
# 임시 폴더 내에 파일 생성
temp_file = os.path.join(temp_dir, 'test.txt')
with open(temp_file, 'w') as f:
f.write('테스트 데이터')
# 블록 종료 시 폴더와 내부 파일 모두 삭제
# 방법 3: 테스트에서 활용
import unittest
class FileProcessorTest(unittest.TestCase):
def test_process_file(self):
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt') as f:
f.write('테스트 입력')
f.flush()
result = process_file(f.name)
self.assertEqual(result, 'expected')
# 테스트 실패해도 임시 파일은 자동 삭제
# 실무 예제: 대용량 데이터 처리
def process_large_data(data):
with tempfile.NamedTemporaryFile(mode='w+b', suffix='.dat') as temp:
# 메모리에 담기 힘든 데이터를 임시 파일로
temp.write(data)
temp.seek(0)
# 파일에서 조금씩 읽어서 처리
while chunk := temp.read(4096):
process_chunk(chunk)
김개발 씨는 파일 처리 함수의 단위 테스트를 작성하고 있었습니다. 테스트용 임시 파일이 필요했습니다.
처음에는 그냥 파일을 만들고 테스트 끝나고 삭제했습니다. python def test_read_file(): with open('test_temp.txt', 'w') as f: f.write('test data') result = read_file('test_temp.txt') assert result == 'test data' os.remove('test_temp.txt') # 삭제 문제는 테스트가 실패하면 os.remove 라인이 실행되지 않는다는 점이었습니다.
임시 파일이 계속 남아있었습니다. 테스트를 여러 번 실행하면 임시 파일이 쌓여만 갔습니다.
박시니어 씨가 tempfile 모듈을 소개했습니다. tempfile은 Python 표준 라이브러리로, 임시 파일과 폴더를 안전하게 관리합니다.
python import tempfile with tempfile.NamedTemporaryFile(mode='w', delete=True) as f: f.write('test data') f.flush() # 테스트 수행 result = read_file(f.name) # 블록 종료 시 자동 삭제 delete=True 옵션이 기본값입니다. with문 블록이 끝나면 파일이 자동으로 삭제됩니다.
테스트가 실패해도, 예외가 발생해도 파일은 반드시 삭제됩니다. 김개발 씨는 다른 기능도 궁금했습니다.
"임시 폴더도 만들 수 있나요?" 물론입니다. TemporaryDirectory를 사용하면 됩니다.
python with tempfile.TemporaryDirectory() as temp_dir: # 임시 폴더에 파일 여러 개 생성 file1 = os.path.join(temp_dir, 'data1.txt') file2 = os.path.join(temp_dir, 'data2.txt') with open(file1, 'w') as f: f.write('data 1') with open(file2, 'w') as f: f.write('data 2') # 처리 수행 process_directory(temp_dir) # 블록 종료 시 폴더와 내부 파일 모두 삭제 이것은 정말 편리합니다. 여러 파일이 필요한 테스트에서 특히 유용합니다.
실무에서 tempfile은 다양하게 활용됩니다. 첫째, 대용량 데이터 처리입니다.
메모리에 한 번에 올릴 수 없는 큰 데이터를 임시 파일에 저장하고 조금씩 읽어서 처리합니다. python def process_huge_data(data_stream): with tempfile.NamedTemporaryFile(mode='w+b') as temp: # 스트림 데이터를 임시 파일로 for chunk in data_stream: temp.write(chunk) temp.seek(0) # 처음부터 읽기 # 조금씩 읽어서 처리 while chunk := temp.read(8192): yield process(chunk) 둘째, 파일 업로드 처리입니다.
웹 애플리케이션에서 업로드된 파일을 임시로 저장할 때 보안상 tempfile을 사용합니다. python def handle_upload(uploaded_file): with tempfile.NamedTemporaryFile(delete=False) as temp: temp.write(uploaded_file.read()) temp_path = temp.name # 비동기 처리를 위해 경로만 전달 process_async.delay(temp_path) 이 경우 delete=False로 설정했습니다.
나중에 처리가 끝나면 수동으로 삭제해야 합니다. 김개발 씨는 이제 테스트 코드에서 더 이상 임시 파일 정리를 걱정하지 않아도 되었습니다.
tempfile과 with문이 모든 것을 처리해 주니까요.
실전 팁
💡 - delete=False로 설정하면 수동으로 삭제해야 합니다 (비동기 처리 시 유용)
- TemporaryDirectory는 폴더 내 모든 파일을 삭제합니다
6. with문과 함께 쓰는 유용한 패턴들
김개발 씨가 with문을 계속 쓰다 보니 여러 가지 활용 패턴이 궁금해졌습니다. 파일 외에도 with문을 쓸 수 있는 곳이 많을 것 같았습니다.
박시니어 씨가 실무에서 자주 쓰는 패턴들을 정리해 주었습니다.
with문은 파일뿐 아니라 데이터베이스 연결, 락, 타이머, 리디렉션 등 다양한 상황에서 활용할 수 있습니다. 컨텍스트 매니저 패턴을 이해하면 무궁무진하게 응용할 수 있습니다.
다음 코드를 살펴봅시다.
# 패턴 1: 데이터베이스 트랜잭션
import sqlite3
@contextmanager
def db_transaction(db_path):
conn = sqlite3.connect(db_path)
try:
yield conn
conn.commit()
except:
conn.rollback()
raise
finally:
conn.close()
# 패턴 2: 실행 시간 측정
import time
from contextlib import contextmanager
@contextmanager
def timer(name):
start = time.time()
yield
print(f"{name}: {time.time() - start:.2f}초")
with timer("데이터 처리"):
process_large_dataset()
# 패턴 3: 디렉토리 변경 후 복구
@contextmanager
def change_directory(path):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)
# 패턴 4: 파일 락
import fcntl
@contextmanager
def file_lock(lock_file):
with open(lock_file, 'w') as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
yield
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
김개발 씨는 with문이 파일에만 쓰는 건 줄 알았습니다. 하지만 박시니어 씨는 with문이 훨씬 더 다양하게 활용될 수 있다고 했습니다.
핵심은 "진입할 때 뭔가 하고, 나갈 때 뭔가 하는" 상황이면 어디든 with문을 쓸 수 있다는 것입니다. 가장 먼저 살펴볼 것은 데이터베이스 트랜잭션입니다.
python @contextmanager def transaction(conn): try: yield conn conn.commit() except: conn.rollback() raise 이 컨텍스트 매니저를 사용하면 트랜잭션 관리가 깔끔해집니다. python with transaction(db_conn) as conn: conn.execute("INSERT ...") conn.execute("UPDATE ...") # 성공하면 commit, 실패하면 rollback 두 번째는 실행 시간 측정입니다.
python @contextmanager def timer(name): start = time.perf_counter() yield elapsed = time.perf_counter() - start print(f"{name}: {elapsed:.4f}초") 이제 어떤 코드든 감싸서 시간을 측정할 수 있습니다. python with timer("API 호출"): response = requests.get(url) # "API 호출: 0.5234초" 출력 세 번째는 작업 디렉토리 변경입니다.
python @contextmanager def working_directory(path): old_cwd = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old_cwd) 특정 디렉토리에서 작업하고 다시 돌아와야 할 때 유용합니다. python with working_directory("/tmp/build"): os.system("make") os.system("make install") # 자동으로 원래 디렉토리로 복귀 네 번째는 파일 락입니다.
여러 프로세스가 같은 파일에 접근할 때 충돌을 막습니다. python import fcntl @contextmanager def file_lock(filepath): with open(filepath, 'w') as f: fcntl.flock(f.fileno(), fcntl.LOCK_EX) try: yield finally: fcntl.flock(f.fileno(), fcntl.LOCK_UN) 다섯 번째는 표준 출력 리디렉션입니다.
python from contextlib import redirect_stdout import io @contextmanager def capture_stdout(): buffer = io.StringIO() with redirect_stdout(buffer): yield buffer 여섯 번째는 환경 변수 임시 변경입니다. python @contextmanager def env_var(key, value): old_value = os.environ.get(key) os.environ[key] = value try: yield finally: if old_value is None: del os.environ[key] else: os.environ[key] = old_value 김개발 씨는 이 모든 패턴을 보면서 with문의 가능성에 놀랐습니다.
"이거 은근 많이 쓰이네요." 박시니어 씨가 웃으며 말했습니다. "컨텍스트 매니저는 파이썬의 숨은 보석이에요.
잘 활용하면 코드가 훨씬 깔끔해져요." 공통점이 보이시나요? 모든 패턴이 "진입 시 설정, 진출 시 정리" 구조를 가지고 있습니다.
이것이 컨텍스트 매니저의 본질입니다.
실전 팁
💡 - 표준 라이브러리 contextlib에 이미 유용한 도구들이 많습니다
- 복잡한 설정/정리 로직이 있다면 컨텍스트 매니저로 분리하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
프로덕션 에이전트 구축 완벽 가이드
실무에서 바로 활용할 수 있는 프로덕션급 AI 에이전트 구축 방법을 처음부터 끝까지 다룹니다. 아키텍처 설계부터 배포, 모니터링까지 실전 경험을 담은 완벽 가이드입니다.
vLLM 통합 완벽 가이드
대규모 언어 모델 추론을 획기적으로 가속화하는 vLLM의 설치부터 실전 서비스 구축까지 다룹니다. PagedAttention과 연속 배칭 기술로 GPU 메모리를 효율적으로 활용하는 방법을 배웁니다.
Web UI Demo 구축 완벽 가이드
Gradio를 활용하여 머신러닝 모델과 AI 서비스를 위한 웹 인터페이스를 구축하는 방법을 다룹니다. 코드 몇 줄만으로 전문적인 데모 페이지를 만들고 배포하는 과정을 초급자도 쉽게 따라할 수 있도록 설명합니다.
Sandboxing & Execution Control 완벽 가이드
AI 에이전트가 코드를 실행할 때 반드시 필요한 보안 기술인 샌드박싱과 실행 제어에 대해 알아봅니다. 격리된 환경에서 안전하게 코드를 실행하고, 악성 동작을 탐지하는 방법을 단계별로 설명합니다.
Voice Design then Clone 워크플로우 완벽 가이드
AI 음성 합성에서 일관된 캐릭터 음성을 만드는 Voice Design then Clone 워크플로우를 설명합니다. 참조 음성 생성부터 재사용 가능한 캐릭터 구축까지 실무 활용법을 다룹니다.