모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

39강 / 전체 40강

파일 I/O와 컨텍스트 매니저 — with 문의 내부

11분 읽기 조회 0

open()과 with 문의 컨텍스트 매니저 프로토콜 원리를 이해하고 pathlib로 현대적 파일 처리를 완전히 마스터한다.

🎯 이 강의에서 배우는 것

파일 처리는 모든 실용 프로그램의 기초입니다. 파이썬의 with 문은 파일을 반드시 닫히게 보장하는 컨텍스트 매니저 프로토콜을 사용합니다. __enter__와 __exit__의 내부 원리를 이해하면 데이터베이스 연결, 락(lock), 임시 디렉토리 등 모든 자원 관리에 같은 패턴을 적용할 수 있습니다. 현대적인 파일 경로 처리인 pathlib도 함께 배웁니다.

📂 open()과 파일 모드 완전 분석

import os

# open(file, mode='r', encoding=None, errors='strict', buffering=-1)
# mode 조합:
# 'r'  — 읽기 (기본)
# 'w'  — 쓰기 (기존 파일 덮어씀)
# 'a'  — 추가 (파일 끝에 추가)
# 'x'  — 새 파일 생성 (이미 있으면 FileExistsError)
# 'b'  — 바이너리 모드 (rb, wb, ab)
# '+'  — 읽기+쓰기 (r+, w+, a+)

# 항상 encoding='utf-8' 명시! 기본값은 플랫폼마다 다름
# Windows: cp949, macOS/Linux: UTF-8

# 쓰기
with open("sample.txt", "w", encoding="utf-8") as f:
    f.write("첫 번째 줄\n")
    f.write("두 번째 줄\n")
    f.writelines(["세 번째\n", "네 번째\n"])

# 읽기 — 전체
with open("sample.txt", "r", encoding="utf-8") as f:
    content = f.read()    # 전체 문자열
    print(repr(content))

# 읽기 — 줄 단위 (대용량 파일에 유리)
with open("sample.txt", "r", encoding="utf-8") as f:
    for i, line in enumerate(f, 1):    # 파일 객체 자체가 이터러블
        print(f"줄 {i}: {line}", end="")

# 위치 제어
with open("sample.txt", "r", encoding="utf-8") as f:
    first_line = f.readline()
    pos = f.tell()          # 현재 위치(바이트)
    print(f"첫 줄: {first_line!r}")
    print(f"위치: {pos}")
    f.seek(0)               # 파일 처음으로
    all_content = f.read()

# 정리
os.remove("sample.txt")

# 바이너리 모드: 원시 바이트
with open("test.bin", "wb") as f:
    f.write(b"\x89PNG\r\n")   # PNG 시그니처 (bytes 리터럴)

with open("test.bin", "rb") as f:
    header = f.read(4)
    print(header)   # b'\x89PNG'
os.remove("test.bin")

🔒 with 문 — 컨텍스트 매니저 프로토콜

# with 문의 변환:
# with open("file") as f:
#     ...
# ↓ 내부적으로:
# mgr = open("file")
# f = mgr.__enter__()
# try:
#     ...
# except:
#     if not mgr.__exit__(*sys.exc_info()):
#         raise   # __exit__가 True 반환하면 예외 억제
# else:
#     mgr.__exit__(None, None, None)

# 파일 객체의 __enter__/__exit__ 직접 확인
f = open("temp.txt", "w", encoding="utf-8")
result = f.__enter__()   # f 자신을 반환
print(result is f)        # True
f.__exit__(None, None, None)   # 파일 닫기
import os; os.remove("temp.txt")

# 커스텀 컨텍스트 매니저 구현
class Timer:
    import time
    def __enter__(self):
        import time
        self._start = time.perf_counter()
        return self   # as 변수에 바인딩

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.perf_counter() - self._start
        print(f"경과 시간: {elapsed:.4f}초")
        return False   # False(또는 None) → 예외 전파, True → 예외 억제

with Timer() as t:
    total = sum(range(1_000_000))
# "경과 시간: X.XXXXs" 출력

# 예외 처리 in __exit__
class SuppressZeroDivision:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is ZeroDivisionError:
            print(f"ZeroDivisionError 억제: {exc_val}")
            return True   # 예외 억제 (프로그램 계속)
        return False

with SuppressZeroDivision():
    x = 1 / 0   # 오류 억제됨!
    print("이 줄은 실행 안 됨")   # __exit__가 호출될 때 블록 탈출
print("계속 실행")   # 실행됨

🎁 contextlib — 제너레이터 기반 컨텍스트 매니저

from contextlib import contextmanager, suppress
import os, tempfile

# @contextmanager: 클래스 없이 컨텍스트 매니저 구현
@contextmanager
def managed_file(path: str, mode: str = "w", encoding: str = "utf-8"):
    # yield 이전: __enter__ 역할
    f = open(path, mode, encoding=encoding)
    try:
        yield f    # as 변수에 f 바인딩
    except Exception:
        f.close()
        raise      # 예외 전파
    else:
        f.close()

with managed_file("test.txt") as f:
    f.write("컨텍스트 매니저 테스트")
os.remove("test.txt")

# @contextmanager 임시 디렉토리
@contextmanager
def temp_directory():
    tmpdir = tempfile.mkdtemp()
    try:
        yield tmpdir    # 임시 경로 반환
    finally:
        import shutil
        shutil.rmtree(tmpdir)   # with 블록 탈출 시 자동 삭제

with temp_directory() as tmpdir:
    print(f"임시 디렉토리: {tmpdir}")
    # 이 블록 안에서 파일 생성 — 블록 탈출 후 자동 삭제

# contextlib.suppress: 특정 예외 억제
with suppress(FileNotFoundError):
    os.remove("없는파일.txt")   # FileNotFoundError 무시
print("suppress 이후 계속")

# 여러 컨텍스트 매니저 동시 사용
with open("a.txt", "w") as fa, open("b.txt", "w") as fb:
    fa.write("A파일")
    fb.write("B파일")
for name in ["a.txt", "b.txt"]:
    os.remove(name)

🗂️ pathlib.Path — 현대적 파일 경로 처리

from pathlib import Path

# Path: 운영체제에 상관없이 동작
home = Path.home()                    # 홈 디렉토리
cwd = Path.cwd()                      # 현재 작업 디렉토리
script_dir = Path(__file__).parent    # 현재 스크립트 디렉토리

# 경로 조합 (/ 연산자 오버로딩)
config = home / ".config" / "myapp" / "settings.json"
print(config)   # /Users/name/.config/myapp/settings.json

# 경로 속성
p = Path("/home/user/project/main.py")
print(p.name)        # main.py
print(p.stem)        # main (확장자 제외)
print(p.suffix)      # .py
print(p.parent)      # /home/user/project
print(p.parts)       # ('/', 'home', 'user', 'project', 'main.py')

# 파일 읽기/쓰기 — 한 줄로
text_file = Path("hello.txt")
text_file.write_text("안녕하세요!\n두 번째 줄", encoding="utf-8")
content = text_file.read_text(encoding="utf-8")
print(content)

# 바이너리
bin_file = Path("data.bin")
bin_file.write_bytes(b"\x00\x01\x02\x03")
data = bin_file.read_bytes()
print(data)

# 존재 확인
print(text_file.exists())     # True
print(text_file.is_file())    # True
print(text_file.is_dir())     # False

# 파일 탐색
# current_dir = Path(".")
# for py_file in current_dir.glob("*.py"):   # 현재 폴더의 .py 파일
#     print(py_file)
# for py_file in current_dir.rglob("*.py"):  # 하위 폴더 포함

# 정리
text_file.unlink()   # 파일 삭제 (os.remove 대체)
bin_file.unlink()

⚠️ 자주 하는 실수

  • encoding 생략: open("file.txt")의 기본 인코딩은 플랫폼마다 다릅니다. Windows는 보통 cp949, macOS/Linux는 UTF-8. 항상 encoding="utf-8"을 명시하세요. 특히 한글 파일을 다룰 때 필수입니다.
  • with 없이 파일 열기: f = open(...)으로 열고 예외가 발생하면 파일이 닫히지 않습니다. 항상 with open(...) as f:를 사용하세요. OS의 파일 디스크립터가 고갈될 수 있습니다.
  • 대용량 파일을 한 번에 read(): 1GB 파일에 f.read()를 하면 메모리에 모두 올라옵니다. 대용량 파일은 for line in f:로 한 줄씩 처리하거나 f.read(chunk_size)로 청크 단위로 처리하세요.

📝 정리 및 다음 강의 예고

  • open(): 항상 encoding="utf-8" 명시, 항상 with로 열기.
  • 컨텍스트 매니저: __enter__/__exit__ 프로토콜. with가 예외에도 항상 __exit__ 호출 보장.
  • @contextmanager: 제너레이터로 컨텍스트 매니저 간단 구현. yield 전이 enter, 후가 exit.
  • pathlib.Path: 현대적 경로 처리. /로 경로 조합, read_text/write_text로 한 줄 파일 처리.

다음 강의: 마지막 강의 — JSON/CSV 처리, 모듈·패키지·가상환경. 파이썬 생태계를 완성하고 실전 프로젝트를 위한 구조 설계를 배웁니다.

관련 주제

  • open() 파일모드
  • with문 컨텍스트매니저
  • __enter__·__exit__
  • contextlib.contextmanager
  • pathlib.Path
  • 텍스트 vs 바이너리 모드
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 40강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...