본문 바로가기

더 나은 엔지니어가 되기 위해/파이썬을 파이썬스럽게

슬기로운 파이썬 트릭 5, 6 - 반복과 이터레이션 & 딕셔너리 트릭

이 글은 슬기로운 파이썬 트릭을 읽고 핵심만 빠르게 정리한 글이다.
6, 7장에서 정리할 내용이 적어 한 글에 압축하여 포스팅한다.


6장. 반복과 이터레이션

6장 앞 부분은 다음 파트들로 이루어져 있다.

  • 6.1. 파이썬다운 반복문 작성하기
  • 6.2. 리스트 컴프리핸션 이해하기
  • 6.3. 리스트 분할 트릭과 스시 연산자
  • 6.4. 아름다운 이터레이터
  • 6.5. 제너레이터는 단순화된 이터레이터다.
  • 6.6. 제너레이터 표현식

이 파트들은 너무 간단한 파이썬 내용이거나, 이전에 공부하며 기록했던 내용이라 그냥 과감히 넘어간다.
이터레이터, 제너레이터 관련해 이전에 공부한 기록은 파이썬 클린 코드 1 - 파이썬스러운 코딩을 파이썬 문법 컨셉에 가면 볼 수 있다.


6.7. 이터레이터 체인

이터레이터 체인이란 아래 예와 같은 것이다.

def intergers():
    for i in range(1, 9):
        yield i

def squared(seq):
    for i in seq:
        yield i * i

>>> chain = squared(integers())
>>> list(chain)
[1, 4, 9, 16, 25, 36, 49, 64]
  • chain 은 한 방향으로 흐르는 일련의 파이프라인을 갖고있다.
  • 위 구조에서 파이프라인은 integers 를 거쳐 squared 로, 그리고 마지막에 list 로 실행된다.
  • 파이프라인의 좋은 점은 단계 별로 할 일을 분리하여 처리할 수 있다는 것이다.
  • 또한 데이터 처리가 '한 번에 한 항목씩' 이뤄진다는 것이다. 즉 체인 처리 단계 사이에 버퍼링이 없다.
  • 체인에 새로운 블록을 추가시키고 싶을 때 다음과 같이 쉽게 붙일 수 있다.
def negated(seq):
    for i in seq:
        yield -i

>>> chain = negated(squared(integers()))
>>> list(chain)
[-1, -4, -9, -16, -25, -36, -49, -64]

7장. 딕셔너리 트릭

7.1. 딕셔너리 기본 값

name_for_userid = {
    382: "Alice"
}

# 지양해야 되는 코드
def greeting(userid):
    if userid in name_for_userid:
        return f"Hi {name_for_userid[userid]}"
    else:
        reutrn f"Hi There"

# 더 나은 코드
def greeting(userid):
    return f"Hi, {d.get(userid, 'There')}"
  • 딕셔너리에 없는 Key 값을 처리해야 할 땐, get 메서드나 collections.defaultdict 을 사용하자.

7.2. 재미있고 효과도 좋은 딕셔너리 정렬

>>> xs = {'a': 4, 'c': 2, 'b': 3, 'd': 1}
>>> sorted(xs)
['a', 'b', 'c', 'd']

>>> sorted(xs.items())
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]

>>> sorted(xs.items(), key=lambda x: x[1])
[('d', 1), ('c', 2), ('b', 3), ('a', 4)]

>>> sorted(xs.items(), key=lambda x: x[1], reverse=True)
[('a', 4), ('b', 3), ('c', 2), ('d', 1)]
  • sortedkeyreverse 파라미터의 인자 값을 활용하여 효과적으로 정렬할 수 있다.
    • key 에는 callable 한 객체(즉 함수나 __call__ 메소드를 가진 클래스)를 주면 된다.
    • 이 객체의 반환 값을 기준으로 모든 요소를 비교한다.
    • reversed 에는 bool 값 (즉 TrueFalse) 를 주면 된다. 오름차순, 내림차순을 정할 수 있다.

7.3. 딕셔너리로 switch/case 문 모방하기

def dispatch_dict(operator, x, y):
    return {
        "add": lambda: x + y,
        "sub": lambda: x - y,
        "mul": lambda: x * y,
        "div": lambda: x / y
    }.get(operator, lambda: None)()

>>> dispatch_dict("add", 1, 2)
3
>>> dispatch_dict("root", 1, 2)
None
  • 파이썬에는 switch/case 문이 문법적으로 없다.
  • 위처럼 코딩하면 긴 if 체인을 다루지 않아도 되고, 좀 더 파이써닉하게 사용할 수 있다.
  • 실제 프로덕션에서 쓸 때는, 딕셔너리를 따로 상수로 빼놓아 한 번만 만들어놓아야 좀 더 이상적이다.

7.4. 딕셔너리 표현식의 특이점

>>> {True: 'yes', 1: 'no', 1.0: 'maybe'}
{True: 'maybe'}
  • True, 1, 1.0 은 모두 같은 것으로 취급된다. (True == 1 == 1.0 의 값은 True 다.)
  • 파이썬은 boolint 의 서브 클래스로 취급한다.
  • 딕셔너리에 기존 키 값에 새로운 값이 업데이트 될 때, 값만 업데이트 하고 키 값 자체는 업데이트 하지 않는다.
    (그래서 위 예제에서 키가 1.0 이 아니라 여전히 True 다.)
  • 나도 이번에 공부하며 새로알게 된 거라 신기했음.

7.5. 딕셔너리를 병합하는 많은 방법

# update() 메서드 사용
>>> xs = {'a': 1, 'b': 2}
>>> ys = {'b': 3, 'c': 4}
>>> xs.update(ys)
>>> xs
{'a': 1, 'b': 3, 'c': 4}

# {**dict1, **dict2} 사용
>>> xs = {'a': 1, 'b': 2}
>>> {**xs, **ys}
{'a': 1, 'b': 3, 'c': 4}
  • update{**dict1, **dict2} 형태로 딕셔너리를 병합할 수 있다.
  • 파이썬 3.9 부터는 dict1 | dict2 형태의 문법도 추가된다고 한다.

7.6. 보기 좋은 딕셔너리 출력

# json 모듈 사용
>>> mapping = {'a': 23, 'b': 42, 'c': 0xc0ffee}
>>> import json
>>> json.dumps(mapping, indent=4, sort_keys=True)
'{"a": 23,\n    "b": 42,\n    "c": 12648430\n}'

>>> mapping['d'] = {1, 2, 3}
>>> json.dumps(mapping)
TypeError: Object of type set is not JSON serializable

# pprint 모듈 사용
>>> import pprint
>>> pprint.pprint(mapping)
{'a': 23, 'b': 42, 'c': 12648430, 'd': {1, 2, 3}}
  • json.dumpspprint.pprint 를 사용해서 딕셔너리를 보기좋게 출력할 수 있다.
  • json 은 기본 데이터 타입이 아닌 데이터가 들어올 경우, TypyError 를 내므로 주의해야 한다.

마치며

마지막 8장은 "파이썬다운 생산성 향상 기법" 으로, 다음의 내용들을 다룬다.

  • 8.1. 파이썬 모듈과 객체 탐색
  • 8.2. virtualenv 로 프로젝트 의존성 격리하기
  • 8.3. 바이트코드 내부 엿보기

굳이 정리할 필요 없을 거 같아 스킵하려고 한다.

참고로 나는 virtualenv 안쓰고 pyenv 쓰고 있다. virtualenv 도 써봤는데 pyenv 가 좀 더 정교한 파이썬 버전 잡기 좋은듯... 관련 링크는 여기 참고

이쯤에서 마무리 해본다. 난 꽤 재밌고 쉬우면서도 요점만 딱딱 뽑아준 책이었다고 생각한다.
책을 빌려준 쏘카와 Kyle 에게 Thanks.