Chapter 1

파이썬 기초와 데이터

  • 1.1 PEP 8 스타일 가이드
  • 1.2 컴파일 시점 오류 기대하지 말 것
  • 1.3 도우미 함수로 복잡한 표현식 대체
  • 1.4 인덱싱 대신 다중 대입 언패킹
  • 1.5 단일 원소 튜플에 괄호 사용
  • 1.6 조건식은 단순할 때만
  • 1.7 대입식(:=)으로 반복 줄이기
  • 1.8 match 구조분해 활용과 주의점
  • 1.9 bytes와 str의 차이
  • 1.10 f-문자열을 선호하라
  • 1.11 repr과 str의 차이
  • 1.12 암시적 문자열 연결보다 명시적 연결
  • 1.13 시퀀스 슬라이스 기본기
  • 1.14 스트라이드와 슬라이스를 동시에 쓰지 말 것
  • 1.15 슬라이싱 대신 포괄적 언패킹
  • 1.16 딕셔너리 삽입 순서 의존 주의
  • 1.17 get으로 키 존재 여부 처리
  • 1.18 defaultdict로 내부 상태 관리
  • 1.19 __missing__으로 키 의존적 기본값
  • 1.20 깊은 중첩 대신 클래스 합성

파이썬답게 쓴다는 건, 짧게 쓴다는 게 아니라 의도를 드러낸다는 거야.

다른 언어에서 넘어온 사람이 가장 먼저 부딪히는 벽이 바로 이거거든. C나 Java에서는 컴파일러가 잡아주니까 대충 써도 빌드 시점에 걸러지잖아. 근데 파이썬은 동적 언어라서 대부분의 오류가 런타임에서야 터진다. 타입 어노테이션을 달아도 인터프리터 자체가 강제하지 않아. mypypyright 같은 정적 분석 도구를 별도로 돌려야 하고, 결국 테스트가 컴파일러의 역할을 대신하는 구조야. 그래서 파이썬에서는 코드를 읽기 쉽게 쓰는 게 생존 전략이 되는 거지.

PEP 8 스타일 가이드가 그 출발점인데, 사실 요즘은 black이나 ruff 같은 자동 포매터에 맡기면 돼. 들여쓰기 4칸, snake_case, None 비교에 is 쓰기 — 이런 건 도구가 알아서 해주니까 내용에 집중하자. 진짜 중요한 건 그다음이야. 한 줄짜리 복잡한 표현식을 쓰고 "파이썬답다!"고 착각하는 사람이 많은데, 복잡한 로직을 한 줄에 우겨넣으면 읽기 어렵고 디버깅이 힘들어. 도우미 함수로 빼는 게 맞아. 코드는 쓰는 시간보다 읽는 시간이 훨씬 길거든.

같은 맥락에서 언패킹도 중요해. snacks[0][1] 같은 인덱싱 대신 name, calories = snacks[0] 이렇게 쓰면 각 값이 뭘 의미하는지 바로 보이잖아. 조건식도 a if condition else b 정도는 괜찮지만 중첩하면 금지야. 코드 줄 수를 줄이는 것보다 가독성이 훨씬 중요하다는 게 이 챕터의 핵심이지.

Python 3.8의 **왈러스 연산자(:=)**나 3.10의 match/case 문 같은 새 문법도 마찬가지야. 대입식은 같은 함수를 두 번 호출하는 반복을 제거할 때, match는 딕셔너리나 클래스 인스턴스를 구조분해할 때 빛나는 거지, 단순한 값 비교에 억지로 쓰면 오히려 역효과거든. 새 문법이 있다고 무조건 쓰는 게 아니라, 적절한 때만 쓰는 게 진짜 파이썬다운 거야.

이런 원칙은 데이터를 다루는 기본 타입에서도 그대로 적용돼. 가장 먼저 잡아야 할 건 bytesstr의 구분이지. 파이썬 3에서 이 둘은 완전히 다른 타입이고 섞어 쓸 수 없어. b'hello' + 'world'는 TypeError야. 네트워크나 파일 I/O에서는 bytes로 처리하고, 사용자에게 보여줄 때 str로 변환하는 게 기본 패턴인데, 변환할 때 인코딩을 명시해야 해 — data.decode('utf-8') 이렇게. 생략하면 플랫폼마다 달라져서 이식성 문제가 생기거든.

문자열 포맷팅은 결론이 간단해. f-문자열이 정답이야. C 스타일 %는 인자 순서가 바뀌면 버그가 나고, str.format()은 장황하기만 하지. f'{name} {version}'이 가장 짧고 의도가 명확해. 디버깅할 때는 repr()을 쓰면 좋은데, f-문자열에서 f'{value!r}'로 쓸 수 있어서 편하지. __repr__을 클래스에 정의해두면 디버깅이 훨씬 수월해진다는 것도 기억해두자. 그리고 인접한 문자열 리터럴이 자동으로 합쳐지는 암시적 문자열 연결도 주의해야 해 — 리스트에서 쉼표를 빠뜨리면 두 문자열이 합쳐지는데 에러가 안 나서 찾기 어려운 버그가 되거든.

슬라이스도 "단순하게 쓸수록 좋다"는 원칙이 그대로 적용돼. a[:5], a[3:] 정도는 깔끔하지만, 스트라이드까지 동시에 쓰면 읽기 어려워져. a[2::-1]이 뭘 의미하는지 한눈에 파악이 안 되잖아. 스트라이드가 필요하면 두 단계로 나눠서 하는 게 맞아. 그리고 리스트의 앞쪽과 나머지를 분리할 때는 슬라이싱보다 **포괄적 언패킹(*)**이 더 안전해. first, *rest = numbers 이렇게 쓰면 인덱스 실수도 없고, 원소 수가 바뀌어도 코드를 수정할 필요가 없거든.

문자열과 시퀀스를 넘어서, 딕셔너리도 파이썬의 만능 도구지만 남용하면 유지보수 불가능한 코드가 돼. 키가 없을 때 어떻게 처리하느냐부터 갈리거든. in으로 확인하고 인덱싱하면 키를 두 번 조회하고, try/except KeyError는 과한 느낌이야. get 메서드가 가장 깔끔해 — my_dict.get(key, default) 한 줄이면 끝이고, 조회도 한 번만 일어나. 딕셔너리를 소유하고 관리하는 쪽에서 키가 없을 때 자동으로 기본값을 만들어야 한다면 **defaultdict**가 답이지. setdefault는 이름도 직관적이지 않고 매번 기본값 객체를 생성하는 비효율도 있어서 피하는 게 낫고, 기본값을 만들 때 키 자체가 필요한 경우(캐시 패턴 같은 거)에는 dict를 상속해서 __missing__ 메서드를 정의하면 돼.

근데 진짜 위험한 건 딕셔너리를 너무 많이 쓰는 거야. 딕셔너리 안에 딕셔너리, 그 안에 리스트 — 이런 깊은 중첩 구조는 처음에는 편하지만 금방 관리 불가능해져. 중첩이 2단계를 넘으면 리팩토링 시점이야. **namedtuple이나 dataclass**로 중간 구조를 명시적인 클래스로 만들면 타입 체크도 되고, 메서드도 추가할 수 있고, 코드의 의도가 훨씬 명확해지거든. 딕셔너리는 동적이고 유연한 데이터에 적합하지, 정적 구조를 표현하는 건 클래스의 일이야.


정리

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

  1. 파이썬은 동적 언어니까 가독성이 생존 전략이다. 도우미 함수, 언패킹, 명시적 괄호로 의도를 드러내고, 새 문법은 적절한 때만 쓰자.
  2. bytes/str 구분, f-문자열, 슬라이스는 단순하게. 스트라이드와 동시에 쓰지 말고, 암시적 문자열 연결에 주의하자.
  3. 딕셔너리 중첩이 2단계를 넘으면 클래스로 리팩토링하라. get, defaultdict, __missing__으로 키 접근을 깔끔하게 처리하자.