자료구조, 테스트, 협업
- 7.1 sort의 key 파라미터
- 7.2 sort vs sorted
- 7.3 bisect로 정렬된 시퀀스 검색
- 7.4 deque로 생산자-소비자 큐
- 7.5 heapq로 우선순위 큐
- 7.6 datetime vs time
- 7.7 decimal로 정밀 계산
- 7.8 copyreg과 pickle
- 7.9 TestCase 서브클래스로 테스트
- 7.10 단위 테스트보다 통합 테스트
- 7.11 setUp/tearDown으로 테스트 격리
- 7.12 mock으로 의존성 대체
- 7.13 테스트를 위한 의존성 캡슐화
- 7.14 assertAlmostEqual로 부동소수점 비교
- 7.15 pdb로 대화형 디버깅
- 7.16 tracemalloc으로 메모리 사용 추적
- 7.17 커뮤니티 모듈 활용
- 7.18 venv로 격리된 환경
- 7.19 독스트링 작성
- 7.20 패키지로 모듈 구조화
- 7.21 모듈 영역 코드로 배포 환경 설정
- 7.22 최상위 Exception 정의
- 7.23 순환 의존성 해결
- 7.24 warnings로 마이그레이션 안내
- 7.25 typing 정적 분석
- 7.26 zipapp 대신 오픈소스 번들링
코드를 작성하는 것 너머에는 표준 라이브러리 활용, 테스트, 그리고 팀 협업이라는 세 영역이 기다리고 있어. 이 장에서 정리하지.
정렬부터 보자. sort의 key 파라미터로 정렬 기준을 유연하게 지정할 수 있는데, 여러 기준이 필요하면 튜플을 반환하면 돼. 파이썬의 sort는 안정 정렬이라서 같은 값의 원소는 이전 순서가 유지되니까, 보조 기준으로 먼저 정렬하고 주 기준으로 다시 정렬하는 다단계 정렬도 가능해. sort()는 제자리 정렬이고 sorted()는 새 리스트를 반환하는 차이도 알아둬야 하고.
정렬된 리스트에서 검색할 때는 **bisect**가 O(log n)으로 이진 탐색을 해주고, FIFO 큐가 필요하면 리스트의 pop(0) 대신 **deque**를 써야 해 — 양쪽 끝에서 O(1)이거든. 우선순위 큐는 **heapq**로 최소 힙을 관리하면 되고. 이런 자료구조를 직접 구현하면 코드도 길어지고 버그도 생기는데, 표준 라이브러리가 다 해주잖아.
시간 처리에서는 time 모듈보다 **datetime**이 일관적이고, 내부적으로 UTC를 쓰고 표시할 때만 로컬로 변환하는 게 원칙이야. 금융 계산처럼 정확한 소수 연산이 필요하면 float 대신 **Decimal**을 쓰되, 반드시 문자열로 생성해야 해 — Decimal(0.1)이 아니라 Decimal('0.1'). 직렬화는 pickle + **copyreg**로 클래스 구조가 바뀌어도 하위 호환성을 유지할 수 있지만, 신뢰할 수 없는 데이터에는 절대 pickle을 쓰면 안 돼.
자료구조와 표준 라이브러리를 알았으면, 다음은 테스트야. 파이썬은 동적 언어니까 컴파일러가 못 잡는 만큼 테스트가 더 중요해. 저자가 의외의 조언을 하는데 — 단위 테스트보다 통합 테스트를 선호하라는 거야. 단위 테스트는 구현 세부사항에 결합되어서 리팩토링할 때 깨지는 경우가 많거든. 통합 테스트는 실제 동작 경로를 검증하니까, 구현이 바뀌어도 동작이 같으면 통과해. 물론 복잡한 알고리즘이나 엣지 케이스에는 단위 테스트가 적합하지만, 핵심은 테스트 피라미드에 얽매이지 말고 가성비 좋은 테스트를 쓰라는 것이지.
테스트를 쓸 때 **setUp/tearDown**으로 각 테스트를 격리하는 건 기본이야. 한 테스트의 결과가 다른 테스트에 영향을 주면 순서에 따라 결과가 달라지는 비결정적 테스트(flaky test)가 되는데, 이건 팀의 신뢰를 깎아먹어. 외부 시스템 의존성은 **unittest.mock**으로 대체할 수 있지만, mock을 과도하게 쓰면 테스트가 구현에 결합되는 같은 문제가 생기거든. 더 나은 방법은 의존성 주입으로 설계하는 거야. 의존성을 생성자 인자로 받으면 테스트에서 가짜 구현을 쉽게 넣을 수 있고, @patch보다 결합도가 낮아져. 테스트 용이성은 좋은 설계의 부산물이야.
디버깅도 도구를 알면 달라져. print 디버깅 대신 **pdb**를 쓰면 breakpoint()로 실행을 멈추고 상태를 직접 탐색할 수 있고, 메모리 문제에는 **tracemalloc**이 어느 파일의 몇 번째 줄에서 할당이 일어났는지 추적해줘.
마지막으로 팀 협업 이야기야. venv는 선택이 아니라 필수야. 글로벌에 패키지를 설치하면 프로젝트 간 버전 충돌이 생기고, "내 컴퓨터에서는 되는데"가 시작되거든. requirements.txt로 의존성을 관리하고, 최근에는 poetry나 uv 같은 도구로 더 정교하게 하는 추세지. 커뮤니티 모듈을 적극 활용하되, 의존성을 추가할 때는 유지보수 상태와 라이선스를 확인해야 해 — 마지막 커밋이 3년 전인 라이브러리는 피하자.
코드를 문서화하는 건 독스트링과 타입 어노테이션 두 가지야. 독스트링은 구현이 아니라 인터페이스를 설명해야 하고, 공개 API에 독스트링이 없는 건 사용자에 대한 무례지. 타입 어노테이션은 mypy 같은 정적 분석 도구와 함께 쓰면 런타임 전에 버그를 잡을 수 있어. 파이썬의 동적 특성을 포기하라는 게 아니라, 문서화와 검증을 동시에 하라는 거야. 패키지 구조도 초기에 잘 잡아두면 __init__.py에서 공개 API만 노출해서 내부를 자유롭게 리팩토링할 수 있어.
라이브러리를 만든다면 최상위 Exception 클래스를 정의해서 사용자가 예외를 쉽게 잡을 수 있게 하고, API를 변경할 때는 **warnings.warn**으로 DeprecationWarning을 발생시켜서 점진적으로 알려줘야 해. 순환 의존성이 생기면 그건 모듈 간 책임 분리가 잘못됐다는 신호니까, TYPE_CHECKING 블록이나 지연 임포트로 해결하기보다 설계를 다시 생각하는 게 먼저야. 배포는 zipapp보다 PyInstaller나 Docker 같은 범용 도구가 현실적이고.
정리
7장 읽고 기억할 거 세 가지:
- 표준 라이브러리를 먼저 찾아보라. sort의 key와 안정 정렬, bisect, deque, heapq, datetime, Decimal — 직접 구현하기 전에 이미 있는 걸 쓰자.
- 통합 테스트를 선호하고, 의존성 주입으로 테스트 용이한 설계를 만들라. mock은 절제하고, setUp/tearDown으로 테스트를 격리하자.
- venv는 필수, 독스트링과 타입 어노테이션으로 코드를 문서화하라. 순환 의존성은 설계 문제의 신호이고, warnings로 API 변경을 안내하자.