Effective Python: 좋은 파이썬 코드를 작성하는 125가지 방법
좋은 파이썬 코드를 작성하는 125가지 방법

1. Pythonic Thinking
Item 1: 어떤 파이썬 버전을 쓰는지 알아라
- 신규 프로젝트는
Python 3를 사용해라. - 커맨드라인에서 실행하는
python/python3가 “네가 의도한 파이썬(가상환경)”인지, “시스템 파이썬”인지부터 확인해라. Python 2는 공식적으로 코어 개발자들에게 더이상 메인테인 받지 않는다.
Item 2: PEP 8 스타일 가이드를 따라라
- 파이썬 코드를 작성할 때는 항상
PEP 8을 따라라. - 공통 스타일을 공유하면 파이썬 커뮤니티/팀에서 협업이 쉬워진다.
- 일관된 스타일을 유지해서 코드를 수정하기 쉽게 만들어라.
Black그리고pylint같은 커뮤니티 툴로PEP 8준수를 자동화해라.
Item 3: 파이썬이 항상 컴파일 할때 에러를 감지할거라고 기대하지 마라
- 파이썬은 에러 체크 대부분을
runtime으로 미룬다. - 실행하기 전에는 “당연히 잡힐 것 같은 실수”도 안 잡힐 수 있다고 생각해라.
linter와static analysis로 흔한 에러를 실행 전에 잡아라.
Item 4: 복잡한 표현(코드) 대신 helper function을 작성해라
- 한 줄로 억지로 우겨 넣어서 너무 복잡하게 만들지 마라.
- 복잡한
expression은helper function으로 빼라. - 특히 그 로직을 반복해서 쓸 거면 확실하게 함수로 옮겨라.
Item 5: indexing 말고 unpacking을 선호해라
- 시퀀스를
index로 덕지덕지 접근하지 마라. - 가능한 곳에서는
unpacking으로 여러 값을 한 번에 받아라. unpacking으로 시각적 잡음을 줄이고 가독성을 올려라.
Item 6: 항상 single-element tuple은 괄호로 감싸라
tuple리터럴은 보통 괄호가 optional 이고, trailing comma도 케이스에 따라 들어간다.- single-element
tuple은 원소 뒤에 trailing comma가 필수다. 괄호는 있어도 되고 없어도 된다. - 표현식 끝에 실수로 trailing comma를 붙이면 의미가 single-element
tuple로 바뀐다. 프로그램을 깨먹을 수 있으니 조심해라.
Item 7: 간단한 inline 로직이면 conditional expression을 고려해라
- 파이썬은
if를 표현식 자리에도 넣을 수 있게conditional expression을 제공한다. conditional expression은 다른 언어의 삼항연산자랑 순서가 다르다. 헷갈리지 않게 조심해라.- 애매해지거나 처음 보는 사람이 읽기 어려워지면 쓰지 마라.
- 이득이 확실하지 않으면 일반
if문이랑helper function을 써라.
Item 8: assignment expression으로 반복을 막아라
assignment expression은 walrus operator:=로 “할당 + 평가”를 한 번에 한다. 반복을 줄여라.- 큰 표현식 안의 subexpression으로 쓰면 괄호로 감싸라.
- 파이썬에
switch/case나do/while은 없지만, 이런 패턴 일부는assignment expression으로 더 명확하게 흉내낼 수 있다.
Item 9: 흐름 제어에서 구조 분해가 필요하면 match를 고려해라. if로 충분하면 쓰지 마라
- 단순
if를match로 억지로 바꾸지 마라. 실수하기 쉽고 직관적이지 않은 함정이 있다. match는isinstance체크 + destructuring을 flow control과 같이 묶어야 할 때 강하다. heterogeneous object graph나 semi-structured data 해석할 때 써라.case pattern은list/tuple/dict같은 built-in 구조랑 사용자 정의 클래스에 쓸 수 있다. 근데 타입마다 semantics가 다르다. 바로 안 보이면 함부로 쓰지 마라.
2. Strings and Slicing
Item 10: bytes 와 str의 차이를 알아라
bytes는 8-bit 값의 시퀀스고,str은 유니코드 코드 포인트의 시퀀스다.- 네가 다루는 입력이 어떤 문자 시퀀스 타입인지 보장하려면
helper function을 써라. bytes와str은 같이 섞어서 연산하지 마라(예:>,==,+,%). 애초에 같이 못 쓰는 경우가 많다.- 파일에 binary 데이터를 읽고/쓸 때는 항상 바이너리 모드로 열어라(예:
"rb","wb"). - 파일에 유니코드 텍스트를 읽고/쓸 때는 시스템 기본 인코딩을 믿지 마라.
open에encoding=...을 명시해라.
Item 11: C-style % / str.format 말고 f-string을 써라
%포맷팅은 gotcha도 많고 장황하다. 쓰지 마라.str.format은 포맷 스펙 미니-언어 같은 유용한 개념은 있지만, 결국%의 실수를 반복한다. 피하는 게 낫다.f-string은 문자열에 값을 넣는 최신 문법이고,%방식의 큰 문제를 해결한다.f-string은 짧고 강력하다. 포맷 스펙 안에 임의의 파이썬expression을 바로 넣을 수 있다.
Item 12: 객체 출력할 때 repr vs str 차이를 알아라
- built-in 타입에
print를 호출하면 사람이 읽기 쉬운 문자열(str)이 나온다. 타입 정보 같은 건 숨겨질 수 있다. - built-in 타입에
repr를 호출하면 “출력 가능한 표현”이 나온다. 경우에 따라repr문자열은eval로 다시 원래 값으로 복구될 수도 있다. - 포맷 문자열에서
%s는str처럼 사람이 읽기 쉬운 문자열을 만든다.%r은repr처럼 출력 가능한 표현을 만든다.f-string은 기본이str이고,!r을 붙이면repr로 바뀐다. - 커스텀 클래스는
__repr__과__str__을 정의해라. 디버깅이 쉬워지고, 사람 대상 UI/로그에 객체를 붙이기도 편해진다.
Item 13: 리스트에서는 암시적 문자열 결합 말고 명시적으로 붙여라
- 파이썬은 문자열 리터럴 두 개가 코드에서 붙어 있으면
+가 있는 것처럼 자동으로 합친다(C 언어 스타일과 비슷하다). list/tuple리터럴 안에서는 암시적 결합을 피하라. 작성자 의도가 애매해져서 읽는 사람이 헷갈린다.+로 명시적으로 붙여라.- 함수 호출에서는 positional argument가 1개이고 keyword argument만 추가되는 형태라면 암시적 결합을 써도 괜찮다.
- positional argument가 여러 개라면 암시적 결합을 쓰지 마라.
+로 명시적으로 붙여라.
Item 14: 시퀀스 슬라이싱을 제대로 알아라
- 슬라이싱을 쓸 때 불필요하게 장황하게 쓰지 마라. 시작 인덱스에
0을 넣거나, 끝 인덱스에 시퀀스 길이를 넣지 마라. - 슬라이싱은 범위를 넘어가는 인덱스에 관대하다. 앞/뒤 경계 슬라이스를 쉽게 표현해라(예:
a[:20],a[-20:]). list슬라이스에 값을 할당하면 원본 시퀀스의 해당 구간이 통째로 교체된다. 길이가 달라도 그대로 치환된다.
Item 15: 한 표현식에서 striding과 slicing을 같이 쓰지 마라
- 시작/끝/stride를 한 번에 지정하는 슬라이스는 읽기 어렵고 혼란스럽다.
- stride가 필요하면 start/end 없이 양수의 stride만 쓰는 쪽을 선호해라. 음수 stride는 가능하면 피하라.
- start/end/stride를 한 번에 써야 한다면 두 단계로 나눠라(먼저 stride로 뽑고, 그 다음 slice로 자르기). 아니면
itertools의islice를 써라.
Item 16: slicing보다 catch-all unpacking을 선호해라
unpacking에는 starred expression을 넣을 수 있다. 남는 값들을 한 번에list로 받아라.- starred expression은 패턴 어디에나 둘 수 있다. 결과는 항상 0개 이상 값을 담는
list가 된다. - 리스트를 겹치지 않는 조각으로 나눌 때는, slicing과 indexing을 여러 줄로 쓰지 마라. catch-all
unpacking이 더 덜 위험하고 실수도 줄어든다.
3. Loops 그리고 Iterators
Item 17: range 말고 enumerate를 써라
enumerate는 이터레이터를 돌면서 각 아이템의 인덱스를 같이 받는 문법을 간결하게 제공한다.range로 루프 돌리고 시퀀스를index로 찍는 방식 대신enumerate를 써라.enumerate는 두 번째 인자로 시작 카운트를 지정할 수 있다(기본은0이다).
Item 18: 이터레이터를 병렬로 처리하려면 zip을 써라
zip은 여러 이터레이터를 병렬로 순회할 때 써라.zip은 튜플을 만들어내는 lazy generator다. 무한 입력에도 쓸 수 있다.- 길이가 다른 이터레이터를 넣으면 가장 짧은 쪽에 맞춰서 조용히 잘린다.
- 조용히 잘리는 걸 막고 싶으면
zip에strict=True를 넘겨라. 길이가 다르면runtime에러로 터지게 해라.
Item 19: for/while 뒤에 붙는 else 블록은 피하라
- 파이썬은
for/while바로 뒤에else블록을 붙일 수 있는 특수 문법이 있다. - 루프 뒤
else는 루프 바디가break를 만나지 않았을 때만 실행된다. - 루프 뒤
else는 직관적이지 않다. 헷갈리니까 쓰지 마라.
Item 20: 루프가 끝난 뒤 for 변수는 절대 쓰지 마라
for루프 변수는 루프가 끝나도 현재 스코프에서 접근 가능하다. 그거 믿고 쓰지 마라.- 루프가 한 번도 돌지 않으면
for변수는 스코프에 할당조차 안 될 수 있다. generator expression이랑list comprehension은 기본적으로 루프 변수를 밖으로 새지 않게 만든다.exception handler도 예외 인스턴스 변수를 밖으로 새지 않게 한다.
Item 21: 인자 이터레이션은 방어적으로 해라
- 함수/메서드가 입력 인자를 여러 번 순회하는지 조심해라. 인자가
iterator면 값이 사라지거나 이상한 동작이 난다. - 파이썬
iterator protocol이iter/next와for루프 같은 동작을 어떻게 연결하는지 이해해라. - 너만의 iterable 컨테이너가 필요하면
__iter__를 generator로 구현해라. - 값이
iterator인지 확인하려면iter(x) is x패턴을 써라(같은 객체가 나오면 iterator일 가능성이 크다). 또는isinstance(x, collections.abc.Iterator)로 판별해라.
Item 22: 순회 중에는 컨테이너를 절대 수정하지 마라. 대신 복사본이나 캐시를 써라
list/dict/set을 순회하면서 요소를 추가/삭제하지 마라. 예측하기 어려운runtime에러나 버그가 난다.- 수정이 필요하면 컨테이너의 복사본을 순회해라.
- 성능 때문에 복사를 피해야 하면, 변경 사항을 두 번째 컨테이너(캐시)에 모아뒀다가 나중에 원본에 합쳐라.
Item 23: 효율적인 short-circuit 로직엔 any와 all에 이터레이터를 넘겨라
all은 모든 항목이 truthy면True를 반환한다. falsey를 만나면 즉시 멈추고False를 반환한다.any는 반대로 동작한다. 모든 항목이 falsey면False고, truthy를 만나면 즉시 멈추고True를 반환한다.any/all은 항상True또는False만 반환한다.and/or처럼 “마지막으로 평가된 값”을 반환하지 않는다.any/all에list comprehension을 쓰지 마라.generator expression을 써서 short-circuit 이점을 살려라.
Item 24: 이터레이터/제너레이터 작업엔 itertools를 고려해라
itertools는 이터레이터/제너레이터를 다룰 때 쓰는 표준 도구 모음이다.- 큰 분류는 3가지로 생각해라: 이터레이터 연결, 출력 필터링, 조합 생성.
- 더 고급 함수, 파라미터, 유용한 레시피는 공식 문서를 참고해라.
4. Dictionaries
Item 25: dict 삽입 순서(insertion ordering)에 의존할 땐 조심해라
Python 3.7부터dict를 순회하면 키가 처음 추가된 순서대로 나온다고 기대해도 된다.- 하지만
dict처럼 동작하는 “딕셔너리 비슷한 타입”은dict인스턴스가 아닐 수 있다. 이런 타입은 삽입 순서를 보장한다고 가정하지 마라. -
딕셔너리 같은 클래스에 안전하게 대응하려면 셋 중 하나로 가라.
- 삽입 순서에 의존하지 않는 코드로 짜라.
- 런타임에
dict타입인지 명시적으로 검사해라. - 타입 힌트와
static analysis로 입력을dict로 강제해라.
Item 26: 키가 없을 때는 in/KeyError 말고 get을 우선 써라
- 딕셔너리에서 missing key를 처리하는 흔한 방법은 4개다:
in,KeyError,get,setdefault. - 기본 타입(카운터 같은 것) 다룰 때는
get이 제일 무난하고 깔끔하다. - 기본값 생성 비용이 크거나 예외가 날 수 있으면
get을 쓰고, 필요하면assignment expression으로 중복을 줄여라. setdefault가 좋아 보이면defaultdict가 더 맞는지 먼저 검토해라.
Item 27: 내부 상태 관리에는 setdefault 말고 defaultdict를 써라
- 가능한 키가 계속 늘어날 수 있는 내부 상태용 딕셔너리를 만든다면
collections.defaultdict를 우선 고려해라. - 딕셔너리가 외부에서 들어오는 값이라 네가 생성 과정을 컨트롤 못 하면, 접근은 기본적으로
get으로 해라. - 그래도 코드가 확 짧아지고 기본값 생성 비용이 낮은 일부 상황에서는
setdefault를 써도 된다.
Item 28: 키에 따라 기본값이 달라져야 하면 __missing__을 써라
- 기본값 생성 비용이 크거나 예외가 날 수 있으면
dict.setdefault는 나쁜 선택이다. defaultdict의 factory 함수는 인자를 받을 수 없다. 그래서 “키에 의존하는 기본값”을 만들 수 없다.- 키에 따라 기본값을 만들어야 하면
dict를 상속하고__missing__을 정의해서 처리해라.
Item 29: 딕셔너리/리스트/튜플을 깊게 중첩하지 말고 클래스를 조합해라
- 값이 또 딕셔너리인 딕셔너리, 긴 튜플, 복잡하게 중첩된 built-in 타입 덩어리를 만들지 마라.
- 완전한 클래스가 필요해지기 전까지는 가벼운 데이터 컨테이너로
dataclasses를 먼저 써라. - 내부 상태용 딕셔너리가 복잡해지기 시작하면 bookkeeping 코드를 여러 클래스로 쪼개서 관리해라.
5. Functions
Item 30: 함수 인자는 변형될 수 있다는 걸 알아라
- 파이썬 인자는 reference로 전달된다. 함수/메서드가 인자의 attribute나 내부 값을 바꿀 수 있다.
- 입력을 수정할 거면 이름이랑 문서로 “수정한다”는 걸 분명히 드러내라. 아니면 수정하지 마라.
- 실수로 원본을 건드리기 싫으면, 받은 컬렉션/객체를 복사해서 써라.
Item 31: 3개 초과 언패킹 강요하지 말고 전용 결과 객체를 반환해라
- 함수는 튜플로 여러 값을 반환하고, 호출자는
unpacking으로 받을 수 있다. - catch-all starred expression으로 여러 반환값을 받을 수도 있다.
- 4개 이상 변수로 언패킹하게 만들지 마라. 실수하기 쉽다. 대신 가벼운 클래스 인스턴스를 반환해라.
Item 32: None 반환하지 말고 예외를 던져라
- 특별한 의미로
None을 반환하게 만들지 마라.None은Boolean에서 falsey라서0이나""같은 값이랑 섞이면 버그 난다. - 특수 상황은
None이 아니라 예외로 표현해라. 문서화하고 호출자가 예외를 처리하게 만들어라. - 타입 힌트로 “이 함수는 어떤 경우에도
None을 반환하지 않는다”를 명확히 해라.
Item 33: 클로저 스코프랑 nonlocal을 제대로 알아라
- 클로저 함수는 자신을 감싸는 스코프의 변수를 참조할 수 있다.
- 기본적으로 클로저는 바깥 스코프 변수에 “할당”해서 영향을 주지 못한다.
- 바깥 스코프 변수를 수정해야 하면
nonlocal을 써라. 모듈 레벨 이름을 수정해야 하면global을 써라. nonlocal은 단순한 경우 외에는 남발하지 마라. 복잡해지면 코드가 읽기 어려워진다.
Item 34: 가변 위치 인자로 visual noise를 줄여라
- 함수가 위치 인자를 여러 개 받게 하려면 정의에서
*args를 써라. - 시퀀스의 아이템을 위치 인자로 풀어서 넘기려면 호출에서
*를 써라. - generator에
*를 바로 쓰지 마라. 메모리를 과하게 잡아먹어서 터질 수 있다. *args받는 함수에 새 위치 인자를 추가하지 마라. 호출자 쪽 버그가 조용히 생기기 쉽다.
Item 35: keyword 인자로 옵션 동작을 제공해라
- 함수 인자는 position으로도, keyword로도 넘길 수 있다.
- 위치 인자만 쓰면 의도가 헷갈리는 곳은 keyword로 명확히 해라.
- 기본값 있는 keyword 인자는 기존 호출자를 다 안 고치고도 새 동작을 추가할 수 있게 해준다.
- optional keyword 인자는 항상 keyword로 넘겨라. position으로 넘기지 마라.
Item 36: 동적인 기본값은 None과 docstring으로 지정해라
- 기본 인자 값은 함수 정의 시점(모듈 로드 시점)에 한 번만 평가된다. 동적인 값(함수 호출, 새 객체, 컨테이너)은 이상한 버그를 만든다.
- 동적으로 초기화돼야 하는 기본값은 placeholder로
None을 써라. 실제 기본값 의도는 docstring에 적어라. 함수 바디에서None체크해서 올바른 기본 동작을 트리거해라. Noneplaceholder 방식은 타입 힌트랑 같이 써도 깔끔하게 맞는다.
Item 37: keyword-only / positional-only 인자로 호출 의도를 강제해라
- keyword-only 인자는 특정 인자를 반드시 keyword로 넘기게 만들어서 호출 의도를 명확하게 한다. 인자 리스트에서
*뒤에 둬라(*단독이든*args든 상관없다). - positional-only 인자는 특정 파라미터를 keyword로 못 넘기게 해서 결합도를 줄인다. 인자 리스트에서
/앞에 둬라. /와*사이에 있는 파라미터는 기본 규칙대로 position/keyword 둘 다 허용해라.
Item 38: 데코레이터는 functools.wraps로 정의해라
- 데코레이터는 런타임에 한 함수가 다른 함수를 수정하는 문법이다.
- 데코레이터를 잘못 쓰면 디버거 같은 introspection 도구에서 이상하게 보일 수 있다.
- 커스텀 데코레이터를 만들 때는 반드시
functools.wraps를 써서 메타데이터가 깨지지 않게 해라.
Item 39: glue 함수는 lambda보다 functools.partial을 선호해라
lambda는 인자 재배치나 특정 값 고정으로 인터페이스를 맞출 때 짧게 쓸 수 있다.functools.partial은 위치/키워드 인자를 고정해서 새 함수를 만드는 범용 도구다.- 인자 순서를 바꿔야 하는 경우에만
lambda를 쓰고, 그 외에는partial로 고정해라.
Item 40: map/filter 말고 comprehension을 써라
list comprehension은map/filter보다 보통 더 명확하다.lambda를 강요하지 않기 때문이다.list comprehension은if절로 입력 항목을 쉽게 건너뛸 수 있다.map은 단독으로 이걸 못 한다.dict와set도comprehension으로 만들 수 있다.list comprehension은 평가 시 결과를 전부 메모리에 만든다. 입력이 크면 메모리 많이 먹는다고 생각해라.
Item 41: comprehension에 control subexpression 2개 넘게 넣지 마라
comprehension은 여러 레벨 루프와 여러 조건을 지원한다.- 제어 subexpression이 2개를 넘어가면 읽기 너무 어렵다. 피하라.
Item 42: assignment expression으로 comprehension의 반복을 줄여라
assignment expression은comprehension/generator expression에서 한 번 계산한 값을 같은 식 안에서 재사용하게 해준다. 가독성과 성능을 같이 챙겨라.- 조건 밖에서
assignment expression을 억지로 쓰지 마라. 안정적으로 동작하지 않는 경우가 있다. comprehension에서assignment expression으로 만든 변수는 바깥 스코프로 새어 나간다. 반면comprehension의 루프 변수는 기본적으로 새지 않는다.
Item 43: 리스트 반환 대신 generator를 고려해라
- 누적 결과 리스트를 만들어 반환하는 것보다 generator가 더 명확한 경우가 많다.
- generator는 함수 바디의
yield로 전달한 값들을 순서대로 만들어내는 이터레이터를 반환한다. - generator는 모든 입력/출력을 메모리에 쌓지 않는다. 그래서 입력이 매우 커도 동작하게 만들어라.
Item 44: 큰 list comprehension이면 generator expression을 고려해라
- 입력이 큰데
list comprehension을 쓰면 메모리 때문에 문제가 날 수 있다. generator expression은 출력을 하나씩 만들기 때문에 메모리 문제를 피한다.generator expression은 다른generator expression의for입력으로 넘겨서 조합해라.- 체이닝해도 빠르고 메모리 효율이 좋다.
Item 45: 여러 generator는 yield from으로 합쳐라
yield from은 중첩된 여러 generator를 하나의 generator로 합치는 문법이다.yield from으로 중첩 generator를 수동으로 돌면서yield하는 boilerplate를 없애라.
Item 46: generator에 데이터를 넣으려면 send 말고 “입력 이터레이터”를 인자로 넘겨라
send는 generator에 값을 주입해서yield표현식이 값을 받게 만들 수 있다.send와yield from을 섞지 마라. 예상 못 한 타이밍에None이 튀어나오는 등 놀라운 동작이 생길 수 있다.- generator들을 조합해야 하면
send로 밀어 넣지 말고, 입력 이터레이터를 인자로 전달하는 구조로 짜라.send는 피하라.
Item 47: 상태 전이는 generator throw 말고 클래스로 관리해라
throw는 generator 안에서 “마지막yield지점”에 예외를 다시 던질 수 있다.throw를 쓰면 예외 raise/catch를 위해 중첩과 boilerplate가 늘어난다. 가독성이 깨진다.- 반복 처리 + 상태 전이가 필요하면 stateful 클래스를 만들어라. iteration과 state transition을 메서드로 분리해라.
7. Classes and Interfaces
Item 48: 단순 인터페이스면 클래스 말고 함수를 받아라
- 클래스 정의/인스턴스화 대신, 단순한 컴포넌트 인터페이스는 함수로 해결해라.
- 파이썬에서 함수/메서드 레퍼런스는 first-class다. 다른 타입처럼 표현식에 넣어라.
- 클래스 인스턴스를 함수처럼 호출 가능하게 하려면
__call__을 구현해라. - 상태가 필요한 “함수”가 필요하면 stateful closure로 억지로 만들지 마라.
__call__을 가진 클래스를 고려해라.
Item 49: isinstance 분기 함수 말고 OOP 다형성을 써라
isinstance로 타입을 검사해서 동작을 바꾸는 코드를 남발하지 마라.- 다형성(polymorphism)은 런타임에 “가장 구체적인 서브클래스 구현”으로 메서드 호출을 디스패치하는 OOP 기법이다.
- 많은 타입 분기가 필요하면
isinstance체인 대신 다형성으로 설계해라. 읽기/유지보수/확장/테스트가 쉬워진다.
Item 50: OOP 대신 함수형 스타일이 필요하면 functools.singledispatch를 고려해라
- OOP는 클래스 중심으로 코드가 흩어지기 쉽다. 큰 프로그램에서 관리가 어려워질 수 있다.
- single dispatch는 메서드 다형성 대신 “함수 기반” 동적 디스패치를 제공해서 관련 기능을 한곳에 모으기 좋다.
- 파이썬은
functools에singledispatch데코레이터를 제공한다. 필요하면 써라. - 서로 독립적인 시스템들이 같은 데이터 타입을 대상으로 동작한다면, OOP 대신 single dispatch의 함수형 스타일이 더 맞을 수 있다.
Item 51: 가벼운 클래스 정의엔 dataclasses를 선호해라
dataclasses의@dataclass로 boilerplate 없이 가볍고 실용적인 클래스를 정의해라.- 표준 OOP 문법의 장황함과 실수 포인트를 줄이는 데
dataclasses가 도움이 된다. dataclasses는asdict/astuple같은 변환 헬퍼와field같은 고급 속성 제어도 제공한다.- 나중에 커스터마이즈가 더 필요해지면
dataclasses없이도 OOP 패턴을 직접 구현할 수 있어야 한다. 그 길로 옮길 준비를 해라.
Item 52: @classmethod 다형성으로 객체를 범용적으로 생성해라
- 파이썬 클래스는 생성자가 사실상 하나다.
__init__만 있다고 생각해라. - 대안 생성자가 필요하면
@classmethod로 만들어라. @classmethod다형성으로 “여러 구체 하위 클래스”를 공통 방식으로 생성/연결할 수 있게 해라.
Item 53: 부모 클래스 초기화는 super로 해라
- 파이썬의 표준
MRO(메서드 해석 순서)가 부모 초기화 순서 문제랑 다이아몬드 상속 문제를 정리해준다. - 부모 초기화나 부모 메서드 호출은
super()(인자 0개)로 해라.
Item 54: 기능은 mix-in 클래스로 조합하는 걸 고려해라
mix-in으로 해결 가능하면, 인스턴스 속성이나__init__까지 얹는 다중 상속은 피 해라.- 클래스별 커스터마이징이 필요하면, 인스턴스 단위로 끼울 수 있는 “플러그형 동작”도 같이 고려해라.
mix-in은 필요에 따라 인스턴스 메서드도 넣고, 클래스 메서드도 넣어라.- 작은 동작을
mix-in으로 만들고, 여러 개를 조합해서 복잡한 기능을 구성해라.
Item 55: private 속성보다 public 속성을 선호해라
- 파이썬에서
private속성은 컴파일러가 엄격하게 강제하지 않는다. - 처음부터 “서브클래스가 내부 API/속성을 더 활용할 수 있게” 설계해라. 아예 막아버리는 방향으로 가지 마라.
- 접근을 강제로 막으려 하지 말고,
protected필드는 문서로 규칙을 잡아서 서브클래스가 따라오게 해라. - 서브클래스를 네가 통제 못 하는 상황에서 “이름 충돌”을 피해야 할 때만
private속성을 고려해라.
Item 56: 불변 객체를 생성할 때는 dataclasses를 선호하라
- 불변 객체를 사용하는 함수형 스타일의 코드는 상태를 변경하고 부작용을 일으키는 명령형 스타일의 코드보다 더 견고한 경우가 많다.
- 사용자 정의 불변 객체를 만드는 가장 쉬운 방법은 내장 모듈인
dataclasses를 사용하는 것이다. 클래스 정의 시dataclass데코레이터를 적용하고frozen=True인자를 전달하면 된다. dataclasses모듈의replace헬퍼 함수는 일부 속성만 변경된 불변 객체의 복사본을 생성할 수 있게 해주며, 이를 통해 함수형 스타일의 코드를 더 쉽게 작성할 수 있다.dataclass로 생성된 불변 객체는 값 기준으로 동등성 비교가 가능하고 안정적인 해시 값을 가지므로, 딕셔너리의 키나 집합(set)의 요소로 사용할 수 있다.
Item 57: 사용자 정의 컨테이너 타입을 만들 때는 collections.abc 클래스를 상속하라
- 단순한 사용 사례의 경우,
list나dict와 같은 파이썬의 내장 컨테이너 타입을 직접 상속해도 괜찮다. - 내장 타입을 상속하지 않고 사용자 정의 컨테이너 타입을 올바르게 구현하려면 구현해야 할 메서드의 수가 매우 많다는 점에 주의해야 한다.
- 사용자 정의 컨테이너 클래스가 요구되는 동작을 정확히 따르도록 보장하려면,
collections.abc에 정의된 인터페이스를 상속하도록 하라.
8. Metaclasses and Attributes
Item 58: setter와 getter 메서드 대신 일반 속성을 사용하라
- 새로운 클래스 인터페이스는 단순한 공개 속성(public attribute)을 사용해 정의하고, setter와 getter 메서드 정의는 피하라.
- 객체의 속성에 접근할 때 특별한 동작이 필요하다면
@property를 사용하라. - 최소 놀람의 원칙(rule of least surprise)을 따르고,
@property메서드에서 예상치 못한 부작용을 피하라. @property메서드는 반드시 빠르게 동작해야 한다. I/O를 포함하거나 부작용을 일으키는 느리거나 복잡한 작업은 일반 메서드를 사용하라.
Item 59: 속성을 리팩터링하는 대신 @property 사용을 고려하라
- 기존 인스턴스 속성에 새로운 기능을 부여하기 위해
@property를 사용하라. @property를 활용해 더 나은 데이터 모델로 점진적으로 개선하라.@property를 과도하게 사용하게 된다면, 클래스와 모든 호출 지점을 함께 리팩터링하는 것을 고려하라.
Item 60: 재사용 가능한 @property 메서드를 위해 디스크립터를 사용하라
- 재사용 가능한
@property메서드의 동작과 검증 로직을 위해 사용자 정의 디스크립터 클래스를 정의하라. - 메모리 누수를 방지하기 위해
__set_name__과setattr,getattr를 함께 사용하여 디스크립터에 필요한 데이터를 객체 인스턴스의 딕셔너리에 저장하라. - 속성 접근과 설정 시
__getattribute__가 디스크립터 프로토콜을 어떻게 사용하는지에 대해 정확히 이해하려고 너무 집착하지 말라.
Item 61: Lazy Attribute를 위해 __getattr__, __getattribute__, __setattr__ 사용하기
- 객체의 속성을 지연 로딩하고 저장하기 위해
__getattr__와__setattr__를 사용하라. __getattr__는 존재하지 않는 속성에 접근할 때만 호출되고, 반면__getattribute__는 어떤 속성이든 접근할 때마다 항상 호출된다는 점을 이해하라.- 객체의 속성에 접근하기 위해
super().__getattribute__와super().__getattr__를 호출하여__getattribute__및__setattr__메서드 구현에서 무한 재귀를 피하라.
Item 62: __init_subclass__로 서브클래스 검증하기
- 메타클래스의
__new__메서드는 클래스 구문의 전체 본문이 처리된 이후 실행된다. - 메타클래스는 클래스가 정의된 후 생성되기 전에 클래스를 검사하거나 수정하는 데 사용할 수 있지만, 보통은 필요 이상으로 무겁다.
- 서브클래스가 정의되는 시점(해당 타입의 객체가 생성되기 전)에 올바르게 구성되었는지 보장하기 위해
__init_subclass__를 사용하라. - 여러 계층의 클래스와 다중 상속에서 조합 가능한 검증을 가능하게 하려면, 클래스의
__init_subclass__정의 내부에서 반드시super().__init_subclass__를 호출하라.
Item 63: __init_subclass__로 클래스 등록 자동화
- 클래스 등록(class registration)은 모듈형 파이썬 프로그램을 구성할 때 유용한 패턴이다.
- 메타클래스는 기반 클래스가 상속될 때마다 등록 코드를 자동 실행할 수 있게 해준다.
- 메타클래스를 이용하면 등록 호출을 빠뜨리는 실수를 방지할 수 있다.
- 하지만 표준 메타클래스 방식보다
__init_subclass__사용을 권장한다. 더 명확하고, 초보자에게 이해하기 쉽다.
Item 64: __set_name__로 클래스 속성 어노테이션
- 메타클래스는 클래스가 완전히 정의되기 전에 속성을 수정할 수 있게 해준다.
- 디스크립터(descriptor)와 메타클래스는 선언적 동작 및 런타임 introspection에 강력한 조합이다.
- 디스크립터 클래스에
__set_name__을 정의하면, 자신이 속한 클래스와 속성 이름을 인지할 수 있다.
Item 65: 클래스 본문 정의 순서를 활용해 속성 간 관계 설정하기
- 클래스 본문에서 정의된 속성과 메서드는 런타임에 클래스 객체의
__dict__인스턴스 딕셔너리를 통해 확인할 수 있다. - 클래스 본문의 정의 순서는
__dict__에 그대로 보존된다. 이 특성 덕분에 속성과 메서드의 상대적 위치를 고려하는 코드 작성이 가능하다. 예를 들어 객체 필드를 CSV 컬럼 인덱스에 매핑하는 경우에 특히 유용하다. - 디스크립터와 메서드 데코레이터를 활용하면 정의 순서를 기반으로 프로그램 동작을 더욱 강력하게 제어할 수 있다.
Item 66: 조합 가능한 클래스 확장에는 메타클래스보다 클래스 데코레이터를 선호하라
- 클래스 데코레이터는 클래스를 인자로 받아 새로운 클래스를 반환하거나 기존 클래스를 수정하는 단순한 함수다.
- 최소한의 보일러플레이트로 클래스의 모든 메서드나 속성을 수정하고 싶을 때 유용하다.
- 메타클래스는 함께 조합하기 어렵지만, 여러 클래스 데코레이터는 충돌 없이 동일 클래스에 적용할 수 있다.
9. Concurrency and Parallelism
Item 67: 자식 프로세스 관리는 subprocess를 사용하라
- 자식 프로세스를 실행하고 입출력 스트림을 관리하려면
subprocess모듈을 사용한다. - 자식 프로세스는 파이썬 인터프리터와 병렬로 실행되므로 CPU 코어 활용을 극대화할 수 있다.
- 간단한 사용에는
run()편의 함수를, UNIX 스타일 파이프라인 같은 고급 사용에는Popen클래스를 사용한다. - 데드락이나 멈춤 현상을 방지하려면
communicate()메서드의timeout파라미터를 활용한다.
Item 68: 병렬 처리 목적이라면 스레드를 피하고, Blocking I/O에는 활용하라
- 파이썬 스레드는 GIL(Global Interpreter Lock) 때문에 여러 CPU 코어에서 완전한 병렬 실행이 불가능하다.
- 그럼에도 스레드는 동시에 여러 작업을 수행하는 것처럼 보이게 하는 간단한 방법을 제공한다.
- 계산과 동시에 Blocking I/O를 처리하거나 여러 시스템 호출을 병행할 때 스레드는 여전히 유용하다.
Item 69: 스레드의 데이터 경쟁을 막으려면 Lock을 사용하라
- GIL이 존재하더라도 스레드 간 데이터 경쟁 보호는 개발자의 책임이다.
- 여러 스레드가 동일 객체를 동시에 수정하면 데이터 구조가 손상될 수 있다.
- 프로그램의 불변성을 지키려면
threading모듈의Lock클래스를 사용해 상호 배제를 보장한다.
Item 70: 스레드 간 작업 조율에는 Queue를 사용하라
- 파이프라인 구조는 특히 I/O 중심 프로그램에서 여러 스레드를 활용한 동시 실행을 가능하게 한다.
- 동시성 파이프라인 구현 시 바쁜 대기, 종료 신호 전달, 작업 완료 판단, 메모리 폭증 등의 문제를 고려해야 한다.
Queue클래스는 Blocking 연산, 버퍼 크기 제한, join, 종료 처리 등 안정적인 파이프라인 구축에 필요한 기능을 제공한다.
Item 71: 언제 동시성이 필요한지 판단하라
- 프로그램의 규모와 복잡도가 증가하면 여러 실행 흐름 지원이 필요해진다.
- 가장 흔한 동시성 조율 패턴은 fan-out(작업 분산)과 fan-in(작업 합류)이다.
- 파이썬은 fan-out과 fan-in을 구현할 수 있는 다양한 방법을 제공한다.
Item 72: 필요할 때마다 새로운 Thread 인스턴스 생성을 피해라
- 스레드는 여러 단점을 가진다. 많은 수의 스레드를 시작하고 실행하는 비용이 크고, 각 스레드는 상당한 메모리를 요구하며, 동기화를 위해
Lock같은 별도 도구가 필요하다. Thread는 예외를 스레드를 시작한 코드나 대기 중인 다른 스레드로 직접 전달하는 기본 메커니즘이 없다. 이로 인해 디버깅이 어려워진다.
Item 73: Queue 기반 동시성 사용 시 리팩터링 필요성을 이해하라
- 고정된 수의 워커 스레드와
Queue를 함께 사용하면 fan-out / fan-in 구조의 확장성이 개선된다. - 기존 코드를
Queue기반 구조로 리팩터링하는 작업은 상당한 노력이 필요하며, 특히 파이프라인 단계가 여러 개인 경우 더 복잡해진다. - 고정된 워커 스레드 수는 프로그램이 활용할 수 있는 I/O 병렬성의 상한을 제한한다.
Item 74: 스레드가 필요하다면 ThreadPoolExecutor를 고려하라
ThreadPoolExecutor는 비교적 적은 리팩터링으로 I/O 병렬 처리를 가능하게 한다.- fan-out이 필요할 때마다 새로운 스레드를 생성하는 비용을 줄일 수 있다.
- 스레드 경계를 넘어 예외를 자동으로 전파하므로 디버깅이 쉬워진다.
- 직접 스레드를 사용할 때 발생할 수 있는 메모리 폭증 문제를 완화하지만,
max_workers를 미리 지정해야 하므로 병렬성은 제한된다.
Item 75: 코루틴으로 높은 수준의 I/O 동시성을 달성하라
async키워드로 정의된 함수는 코루틴이다. 호출자는await로 결과를 기다린다.- 코루틴은 수만 개 이상의 작업을 효율적으로 동시에 실행하는 방법을 제공한다.
- fan-out / fan-in 구조로 I/O를 병렬화하면서, 스레드 기반 I/O에서 발생하는 문제들을 피할 수 있다.
Item 76: Thread 기반 I/O를 asyncio로 이전하는 방법을 이해하라
- 파이썬은 코루틴에서 사용할 수 있는 비동기 버전의 for 루프, with 문, 제너레이터, 컴프리헨션, 이터레이터, 그리고 라이브러리 헬퍼 함수를 제공한다.
- 내장 모듈
asyncio는 스레드 및 블로킹 I/O를 사용하던 기존 코드를 코루틴과 비동기 I/O로 이전하는 작업을 단순화한다.
Item 77: asyncio 전환 과정에서 스레드와 코루틴을 함께 활용하라
- 이벤트 루프의 awaitable 메서드
run_in_executor를 사용하면 코루틴에서 동기 함수를ThreadPoolExecutor워커 스레드로 실행할 수 있다. 이는 점진적인 상향식/하향식 마이그레이션을 돕는다. - 이벤트 루프의
run_until_complete메서드는 동기 코드에서 코루틴이 끝날 때까지 실행할 수 있게 한다.asyncio.run_coroutine_threadsafe함수는 스레드 경계를 넘어 동일한 기능을 제공한다.
Item 78: asyncio 이벤트 루프의 응답성을 극대화하라
- 코루틴 내부에서 블로킹 I/O나 스레드 시작 같은 시스템 호출을 수행하면 프로그램 응답성이 저하되고 지연(latency)이 증가할 수 있다.
asyncio.run(..., debug=True)옵션을 사용하면 이벤트 루프의 반응을 방해하는 코루틴을 탐지할 수 있다.- 비동기/동기 경계를 넘나드는 코드의 가독성을 높이기 위해 코루틴 친화적인 인터페이스를 제공하는 헬퍼 스레드 클래스를 고려할 수 있다.
Item 79: 진정한 병렬 처리를 위해 concurrent.futures를 고려하라
multiprocessing모듈은 일부 파이썬 계산 작업을 최소한의 노력으로 병렬화할 수 있는 강력한 도구를 제공한다.- 병렬 처리 기능은
concurrent.futures내장 모듈과ProcessPoolExecutor클래스를 통해 사용하는 것이 가장 단순하다. - 다른 대안을 모두 검토하기 전까지는
multiprocessing의 복잡한 고급 기능 사용을 피하는 편이 좋다.
10. Robustness
Item 80: try/except/else/finally 각 블록을 적극 활용하라
try/finally구문은try블록에서 예외 발생 여부와 관계없이 정리(cleanup) 코드를 실행하게 한다.else블록은try블록에 들어가는 코드를 최소화하고, 성공 경로를 시각적으로 분리하는 데 도움을 준다.else블록은try블록이 성공적으로 끝난 이후,finally이전에 추가 작업을 수행할 때 유용하다.
Item 81: 내부 가정은 assert, 예상 가능한 오류는 raise
raise문은 호출자에게 예상된 오류 상황을 전달하는 데 사용한다.- 함수가 직접 발생시키는 예외는 명시적인 인터페이스의 일부이므로 문서화해야 한다.
assert문은 개발자의 가정을 검증하고 코드 독자에게 의도를 전달하는 용도로 사용한다.- 실패한
assert는 함수의 인터페이스가 아니며 호출자가 처리 대상으로 삼아서는 안 된다.
Item 82: 재사용 가능한 try/finally에는 contextlib와 with
with문은try/finally패턴을 재사용하고 코드의 시각적 복잡도를 줄인다.- 내장 모듈
contextlib의@contextmanager데코레이터를 사용하면 사용자 정의 컨텍스트 매니저를 쉽게 만들 수 있다. - 컨텍스트 매니저에서
yield된 값은with문 내부에서 사용할 수 있다.
Item 83: try 블록은 가능한 짧게 유지하라
try블록에 과도한 코드를 넣으면 의도하지 않은 예외까지 잡힐 수 있다.- 추가 코드는
else블록이나 별도의try구문으로 분리하는 것이 좋다.
Item 84: 예외 변수의 스코프 소멸에 주의하라
except에서 선언된 예외 변수는 해당 블록 내부에서만 유효하다.- 이후 스코프에서 사용하려면 다른 변수에 명시적으로 저장해야 한다.
Item 85: 광범위한 Exception 처리에 주의하라
except Exception:은 프로그램 일부를 보호하는 데 도움이 될 수 있다.- 그러나 의도하지 않은 오류까지 숨길 위험이 있다.
- 광범위 예외 처리 시에는 반드시 로깅 또는 출력으로 가시성을 확보해야 한다.
Item 86: Exception과 BaseException의 차이를 이해하라
- 파이썬 내부 동작에서는
BaseException하위 클래스가 발생할 수 있다. except Exception:은 이를 잡지 않는다.try/finally,with등은BaseException도 정상적으로 처리한다.BaseException을 직접 처리하는 것은 신중해야 한다.
Item 87: 향상된 예외 보고에는 traceback 활용
- 처리되지 않은 예외는 기본적으로 스택 트레이스를 출력한다.
- 고도의 동시성 환경에서는 트레이스백이 불완전하게 보일 수 있다.
traceback모듈을 사용하면 예외 정보를 세밀하게 제어할 수 있다.
Item 88: 명시적 예외 체이닝으로 트레이스백 명확화
except블록에서 새 예외 발생 시 기존 예외는__context__에 저장된다.raise ... from ...구문은 원인 예외를__cause__로 명시한다.- 명시적 체이닝은 오류 원인 분석을 쉽게 만든다.
Item 89: 제너레이터에서 리소스 정리에 주의하라
- 일반 함수와 달리 제너레이터의
finally는 반복 종료 시 실행된다. - 부분 반복 후 참조 해제 시
GeneratorExit예외가 주입된다. - 리소스는 제너레이터 내부 생성보다 외부에서 전달하고 관리하는 편이 안전하다.
Item 90: __debug__를 False로 설정하지 말라
- 기본값
True에서 모든assert가 실행된다. - 최적화 옵션 사용 시
assert는 무시된다. assert는 버그 원인 추적에 중요한 역할을 한다.
Item 91: 개발 도구가 아니라면 exec, eval 사용을 피해라
eval은 문자열 표현식을 실행한다.exec은 코드 블록을 실행하고 스코프에 영향을 준다.- 보안 및 유지보수 위험이 크므로 제한적으로 사용해야 한다.
11. Performance
Item 92: 최적화 전에 반드시 프로파일링하라
- 최적화에 앞서 프로파일링이 중요한 이유는 성능 저하의 원인이 직관적이지 않은 경우가 많기 때문이다.
profile모듈보다 더 정확한 정보를 제공하는cProfile모듈을 사용한다.Profile객체의runcall메서드를 활용하면 함수 호출 트리를 격리된 상태로 측정할 수 있다.Stats객체를 사용하면 필요한 프로파일링 결과만 선택해 분석할 수 있다.
Item 93: 성능 핵심 코드는 timeit으로 마이크로벤치마크하라
- 내장 모듈
timeit은 자료구조 및 알고리즘 선택을 과학적으로 비교하는 데 유용하다. - 신뢰성 있는 측정을 위해 setup 코드를 사용해 초기화 시간을 제외하고, 결과는 비교 가능한 지표로 정규화한다.
python -m timeitCLI를 사용하면 코드 스니펫 성능을 빠르게 확인할 수 있다.
Item 94: 언제 다른 언어로 대체할지 판단하라
- 다른 언어로 재작성하는 것은 타당한 선택일 수 있으나, 먼저 가능한 최적화 기법을 모두 검토해야 한다.
- CPU 병목을 C 확장 모듈이나 네이티브 라이브러리로 옮기면 성능 개선 효과가 크다. 단, 비용과 버그 위험이 따른다.
- 파이썬 생태계에는 적은 변경으로 성능을 향상시키는 다양한 도구와 라이브러리가 존재한다.
Item 95: 네이티브 라이브러리 연동에는 ctypes를 고려하라
- 내장 모듈
ctypes는 다른 언어로 작성된 네이티브 라이브러리를 쉽게 통합할 수 있게 한다. - Python C 확장 API 대비 빌드 복잡도가 낮아 빠른 개발이 가능하다.
- 단, C 타입 시스템 제약으로 인해 Pythonic한 API 설계에는 한계가 있다.
Item 96: 성능과 사용성을 모두 원한다면 확장 모듈을 고려하라
- C로 작성된 확장 모듈은 네이티브 속도로 실행되며 Python API와 긴밀히 통합된다.
- 메모리 관리 및 예외 전파 등 Python API의 특수성은 학습 난이도가 높다.
- Python 프로토콜 및 내장 데이터 타입을 활용하는 것이 확장 모듈의 가장 큰 가치다.
Item 97: 시작 속도 개선을 위해 바이트코드 캐싱을 활용하라
- CPython은 소스 파일을 바이트코드로 컴파일한 뒤 가상 머신에서 실행한다.
- 생성된 바이트코드는 디스크에 캐시되므로, 이후 실행 시 다시 컴파일하지 않아도 된다.
- 프로그램 시작 속도를 최적화하려면 바이트코드 파일을 미리 생성하고, 운영체제 메모리에 캐시된 상태를 유지하는 것이 가장 효과적이다.
Item 98: 동적 import로 모듈을 지연 로딩하라
-X importtime플래그를 사용하면 모듈 및 의존성 로딩 시간을 확인할 수 있다.- 모듈을 함수 내부에서 동적으로 import하면 실제로 기능이 필요할 때까지 초기화를 지연할 수 있다.
- 이미 로드된 모듈을 다시 확인하는 비용은 매우 작기 때문에, 콜드 스타트 지연을 줄일 수 있다면 충분히 감수할 만하다.
Item 99: memoryview와 bytearray로 제로 카피를 활용하라
memoryview는 버퍼 프로토콜을 지원하는 객체의 슬라이스를 복사 없이 읽고 쓸 수 있게 한다.bytearray는 변경 가능한 bytes 유사 타입으로, 네트워크 수신 등에서 효율적인 데이터 처리를 지원한다.memoryview는bytearray를 감싸 임의 위치에 데이터를 복사 없이 삽입할 수 있게 한다.
Data Structures and Algorithms
Item 100: 복잡한 정렬 기준에는 key 파라미터를 활용하라
list의sort메서드는 문자열, 정수, 튜플 등 기본 타입의 자연 순서를 기준으로 정렬한다.- 일반 객체는 특별 메서드로 자연 순서를 정의하지 않는 한 정렬할 수 없다.
key파라미터에 함수를 전달하면 각 원소를 대체할 값을 기준으로 정렬할 수 있다.key함수가 튜플을 반환하면 여러 정렬 기준을 결합할 수 있다. 단항 마이너스(-)를 사용하면 개별 기준의 정렬 방향을 반전할 수 있다.- 음수 변환이 불가능한 타입은 우선순위가 낮은 기준부터 높은 기준 순으로 여러 번
sort를 호출해 해결한다.
Item 101: sort와 sorted의 차이를 이해하라
sort는 제자리(in-place) 정렬로 메모리 사용이 적고 성능이 가장 좋다.sorted는 모든 이터러블을 입력으로 받을 수 있으며 원본 데이터를 변경하지 않는다.
Item 102: 정렬된 시퀀스 검색에는 bisect를 고려하라
- 정렬된 리스트에서
index나 반복문으로 검색하면 선형 시간(O(n))이 걸린다. bisect모듈의bisect_left함수는 이진 탐색을 사용해 로그 시간(O(log n))에 값을 찾는다.
Item 103: 생산자-소비자 큐에는 deque를 사용하라
list로 FIFO 큐를 구현하면append와pop(0)을 사용할 수 있다.- 그러나
pop(0)은 큐 길이가 길어질수록 성능이 급격히 저하된다. collections모듈의deque는append와popleft가 항상 상수 시간(O(1))이므로 큐에 적합하다.
Item 104: 우선순위 큐에는 heapq를 활용하라
- 우선순위 큐는 중요도 순으로 항목을 처리한다.
list연산으로 구현하면 큐가 커질수록 성능이 비효율적이다.heapq모듈은 효율적으로 확장 가능한 우선순위 큐 구현을 지원한다.- 우선순위를 비교하려면 항목이 자연 정렬 순서를 가져야 하며, 필요하다면
__lt__같은 메서드를 정의한다.
Item 105: 로컬 시간 처리에는 datetime을 사용하라
- 시간대 변환에
time모듈 사용을 피한다. datetime과zoneinfo모듈을 사용해 신뢰성 있게 시간대 변환을 수행한다.- 내부 표현은 항상 UTC로 유지하고, 표시 직전에만 로컬 시간으로 변환한다.
Item 106: 정밀도가 중요하면 decimal을 사용하라
- 파이썬은 다양한 수치 타입을 제공한다.
Decimal클래스는 금전 계산처럼 높은 정밀도와 반올림 제어가 필요한 경우에 적합하다.- 정확한 계산이 필요하면
float대신str을Decimal생성자에 전달한다.
Item 107: pickle 직렬화는 copyreg로 유지보수성을 확보하라
pickle은 신뢰할 수 있는 프로그램 간 객체 직렬화에 적합하다.- 클래스 구조가 변경되면 이전에 직렬화된 데이터 역직렬화가 깨질 수 있다.
copyreg모듈을 사용해 직렬화 방식을 등록하면 하위 호환성을 유지할 수 있다.
13. Testing and Debugging
Item 108: TestCase 하위 클래스로 관련 동작을 검증하라
unittest모듈의TestCase를 상속하고, 테스트할 동작마다 하나의 메서드를 정의한다. 테스트 메서드 이름은 반드시test로 시작해야 한다.- 내장
assert대신assertEqual등TestCase가 제공하는 헬퍼 메서드를 사용해 기대 동작을 검증한다. - 반복되는 테스트 케이스는
subTest를 활용해 데이터 기반 테스트로 작성한다.
Item 109: 단위 테스트보다 통합 테스트를 우선하라
- 통합 테스트는 여러 컴포넌트가 함께 동작하는 방식을 검증하고, 단위 테스트는 개별 컴포넌트만 검증한다.
- 파이썬의 동적 특성상 통합 테스트가 프로그램의 정확성에 대한 신뢰를 얻는 가장 좋은 방법이다.
- 엣지 케이스가 많은 부분은 단위 테스트로 보완한다.
Item 110: setUp, tearDown으로 테스트를 격리하라
TestCase의setUp과tearDown을 사용해 각 테스트가 독립적으로 실행되도록 환경을 초기화한다.- 통합 테스트에서는 모듈 단위의
setUpModule,tearDownModule을 사용해 테스트 모듈 전체 생명주기를 관리한다.
Item 111: 복잡한 의존성은 Mock으로 대체하라
unittest.mock모듈의Mock클래스를 사용하면 의존성 동작을 시뮬레이션할 수 있다.assert_called_once_with계열 메서드로 호출 방식까지 검증한다.- 키워드 전용 인자와
patch함수를 활용해 테스트 대상 코드에 Mock을 주입한다.
Item 112: Mock을 쉽게 하도록 의존성을 캡슐화하라
- Mock 설정이 반복된다면 의존성을 별도 클래스로 캡슐화해 테스트를 단순화한다.
Mock은 클래스를 흉내 내며, 호출 시 또 다른 Mock을 반환해 메서드처럼 동작한다.- 엔드투엔드 테스트를 위해 의존성 주입 지점을 명확히 분리하는 리팩터링을 고려한다.
Item 113: 부동소수점 비교에는 assertAlmostEqual을 사용하라
- 연산 순서에 따라 부동소수점 결과는 미세하게 달라질 수 있다.
assertEqual은 전체 정밀도를 비교하므로 테스트가 불안정해질 수 있다.assertAlmostEqual,assertNotAlmostEqual로 허용 오차를 지정한다.
Item 114: pdb로 인터랙티브 디버깅하라
- 코드 중간에
breakpoint()를 호출하면 디버거를 시작할 수 있다. pdb명령어로 실행 제어와 상태 확인을 반복할 수 있다.python -m pdb또는pdb.pm()으로 예외 발생 이후 디버깅할 수 있다.
Item 115: 메모리 분석에는 tracemalloc을 활용하라
- 파이썬 프로그램의 메모리 사용과 누수를 파악하기는 쉽지 않다.
gc모듈은 객체 존재 여부는 알 수 있지만, 할당 위치 정보는 제공하지 않는다.tracemalloc모듈은 메모리 사용의 원인을 추적하는 강력한 도구다.
14. Collaboration
Item 116: 커뮤니티 패키지 위치를 파악하라
- Python Package Index(PyPI)는 커뮤니티가 유지·관리하는 다양한 패키지를 제공한다.
pip명령어로 PyPI 패키지를 설치할 수 있다.- 대부분의 PyPI 패키지는 무료 오픈소스 소프트웨어다.
Item 117: 격리되고 재현 가능한 의존성에는 가상 환경을 사용하라
- 가상 환경을 사용하면 동일한 머신에서 동일 패키지의 여러 버전을 충돌 없이 설치할 수 있다.
python -m venv로 생성하고,bin/activate로 활성화하며,deactivate로 비활성화한다.pip freeze로 의존성을 저장하고,pip install -r requirements.txt로 동일 환경을 재현한다.
Item 118: 모든 함수·클래스·모듈에 독스트링을 작성하라
- 모든 모듈, 클래스, 함수에 독스트링을 작성하고 코드 변경에 맞춰 유지한다.
- 모듈 독스트링에는 주요 클래스와 함수의 개요를 설명한다.
- 클래스 독스트링에는 동작 방식, 중요한 속성, 상속 관련 내용을 문서화한다.
type애너테이션을 사용한다면 중복 정보를 독스트링에 반복하지 않는다.
Item 119: 패키지로 모듈을 구성하고 안정적인 API를 제공하라
- 패키지는 다른 모듈을 포함하는 모듈이다. 고유한 절대 경로 이름으로 네임스페이스를 분리한다.
- 디렉터리에
__init__.py파일을 추가하면 패키지가 된다. __all__특수 속성으로 외부에 공개할 API를 명시할 수 있다.__init__.py에서 공개 이름만 import하거나 내부용 이름에 언더스코어를 붙여 구현 세부를 숨긴다.- 단일 팀·단일 코드베이스에서는
__all__이 필수는 아니다.
Item 120: 모듈 스코프 코드를 활용해 배포 환경을 구성하라
- 프로그램은 서로 다른 배포 환경에서 실행될 수 있다.
- 모듈 스코프에서 일반 파이썬 코드를 사용해 환경별 설정을 적용한다.
sys,os모듈 등을 통해 외부 조건에 따라 모듈 내용을 구성할 수 있다.
Item 121: API 보호를 위해 루트 예외를 정의하라
- 모듈에서 공통 루트 예외를 정의하고 그 하위 클래스만 발생시키면 호출자가 예외를 쉽게 처리할 수 있다.
- 루트 예외를 잡으면 API 소비 코드의 버그를 찾는 데 도움이 된다.
Exception을 잡으면 API 구현 내부 버그를 찾는 데 유용하다.- 중간 루트 예외를 두면 향후 더 구체적인 예외를 추가해도 호환성을 유지할 수 있다.
Item 122: 순환 의존성을 끊는 방법을 이해하라
- 두 모듈이 import 시점에 서로를 참조하면 순환 의존성이 발생해 프로그램이 시작 시 실패할 수 있다.
- 공통 의존성을 하위 모듈로 분리해 구조를 재설계한다.
- 최소한의 리팩터링으로 해결하려면 동적 import를 사용한다.
Item 123: 리팩터링과 마이그레이션에는 warnings를 활용하라
warnings모듈로 API의 deprecated 사용을 알릴 수 있다.-W error옵션을 사용하면 경고를 예외로 처리해 테스트에서 문제를 조기에 발견할 수 있다.- 운영 환경에서는 경고를 로깅 시스템과 연동한다.
- 경고가 적절한 시점에 발생하는지 테스트로 검증한다.
Item 124: 정적 분석을 위해 typing을 활용하라
typing모듈과 타입 애너테이션 문법으로 타입 정보를 명시한다.- 정적 타입 검사 도구는 런타임 이전에 많은 버그를 발견하도록 돕는다.
- 생산성을 해치지 않도록 점진적으로 도입하고 API 설계와 함께 고려한다.
Item 125: 프로그램 번들링에는 오픈소스 도구를 우선하라
- 파이썬은
zip아카이브에서 직접 모듈을 로드할 수 있다. - 하지만 많은 패키지가 데이터 파일 및 확장 모듈 의존성 때문에
zip환경에서 정상 동작하지 않는다. zipapp대신 Pex 같은 오픈소스 도구를 활용하면 배포 편의성과 안정성을 동시에 확보할 수 있다.
Leave a comment