Chapter 6

강건성과 성능

  • 6.1 try/except/else/finally 블록 활용
  • 6.2 assert와 raise 구분
  • 6.3 contextlib과 with 문
  • 6.4 try 블록은 짧게
  • 6.5 예외 변수 소멸 주의
  • 6.6 Exception 잡기 주의
  • 6.7 Exception vs BaseException
  • 6.8 traceback으로 예외 리포팅
  • 6.9 예외 체이닝(from)
  • 6.10 제너레이터 자원 관리
  • 6.11 __debug__를 False로 설정하지 말 것
  • 6.12 exec과 eval 금지
  • 6.13 프로파일링 후에 최적화
  • 6.14 timeit 마이크로벤치마크
  • 6.15 다른 언어로 대체할 시점
  • 6.16 ctypes로 네이티브 라이브러리 통합
  • 6.17 확장 모듈로 성능 극대화
  • 6.18 바이트코드 캐시와 파일 시스템 캐싱
  • 6.19 지연 로딩과 동적 임포트
  • 6.20 memoryview와 bytearray

프로덕션 코드에서 가장 중요한 건 "잘 돌아가는 것"이 아니라 "문제가 생겼을 때 제대로 처리하고, 성능 병목을 정확히 짚는 것"이야.

try 문의 네 블록 — try, except, else, finally — 을 전부 활용하는 게 출발점이지. 핵심은 try를 짧게 쓰는 거야. try에 코드를 많이 넣으면 어느 줄에서 예외가 발생했는지 특정하기 어렵고, except가 의도하지 않은 예외까지 삼키게 돼. 예외가 발생할 수 있는 딱 그 코드만 try에 넣고, 후속 로직은 else에, 정리 코드는 finally에 넣는 게 맞아. except Exception도 주의해야 하는데, TypeErrorAttributeError 같은 프로그래밍 오류까지 잡아서 버그를 숨기거든. 가능하면 구체적인 예외 타입을 지정하자.

assertraise의 구분도 중요해. assert는 "이건 반드시 참이어야 해"라는 내부 가정이고, raise는 "이런 입력은 처리할 수 없어"라는 기대 위반이야. 결정적 차이는 assert가 python -O(최적화 모드)에서 완전히 제거된다는 거지. 그러니까 assert를 입력 검증에 쓰면 최적화 모드에서 검증이 사라지는 보안 구멍이 생겨. 예외를 다른 예외로 감쌀 때는 raise ... from e예외 체이닝을 하면 트레이스백에서 원인을 추적할 수 있고, try/finally 패턴이 반복되면 contextlib의 **@contextmanager**로 컨텍스트 매니저를 만드는 게 깔끔해.

execeval은 절대 쓰지 마. 외부 입력을 eval에 넣으면 코드 인젝션 공격에 노출되고, 설정 파일 파싱이 목적이면 json이나 yaml을 쓰면 돼. 제너레이터가 자원(파일 핸들 등)을 보유하고 있을 때는 소비자가 끝까지 소비하지 않으면 자원이 해제되지 않으니까, 자원의 소유권을 명확히 하는 게 중요해.

강건한 코드를 만든 다음에는 성능을 볼 차례인데, 원칙은 하나야 — 추측하지 말고, 측정하라. 직감으로 "여기가 느릴 것 같은데"라고 추측하면 높은 확률로 틀려. **cProfile**로 병목을 먼저 찾아야 해. 각 함수의 호출 횟수, 총 소요 시간을 보여주니까 실제로 어디서 시간이 걸리는지 알 수 있지. 작은 코드 조각을 비교할 때는 **timeit**이 가비지 컬렉션을 멈추고 안정적인 결과를 주지만, 마이크로벤치마크 결과가 실제 애플리케이션 성능과 다를 수 있으니까 참고용으로만 쓰자.

프로파일링해서 병목을 찾았는데 파이썬으로 충분히 최적화가 안 되면, 핫스팟만 다른 언어로 대체하는 전략을 쓰는 거야. Cython으로 C 컴파일하거나, Rust + PyO3로 바인딩을 만들거나, **ctypes**로 기존 C 라이브러리를 바로 호출할 수 있어. 핵심은 전체를 다시 쓰는 게 아니라, 병목 부분만 네이티브로 가속하는 거지. 파이썬의 생산성은 유지하면서 성능 크리티컬한 곳만 손대는 접근이야.

시작 시간이 문제라면 바이트코드 캐시(__pycache__)를 이해하고, compileall로 미리 컴파일해두거나, 무거운 모듈은 함수 안에서 지연 임포트하면 돼. 대용량 바이너리 데이터를 다룰 때는 **memoryviewbytearray**로 복사 없이(zero-copy) 데이터를 조작할 수 있어서 메모리 사용량과 성능을 크게 개선할 수 있지.


정리

6장 읽고 기억할 거 세 가지:

  1. try는 짧게, assert는 내부 가정, raise는 외부 입력 검증이다. 구체적인 예외 타입을 잡고, exec/eval은 절대 쓰지 마라.
  2. 추측하지 말고 측정하라. cProfile로 병목을 찾고, 파이썬이 느리면 핫스팟만 ctypes나 C 확장으로 대체하자.
  3. 시작 시간은 바이트코드 캐시와 지연 로딩으로, 바이너리 성능은 memoryview로. 예외 체이닝(from)으로 원인을 추적 가능하게 하자.