Home 파이썬 코딩의 기술(4) 컴프리헨션과 제너레이터
Post
Cancel

파이썬 코딩의 기술(4) 컴프리헨션과 제너레이터

  • 책 파이썬 코딩의 기술을 읽고 정리한 내용입니다. Book_Logo

    4. 컴프리헨션과 제너레이터

    27. map과 filter 대신 컴프리헨션을 사용하라

    map과 filter를 사용하는 것보다 더 명확하다.

    1
    2
    3
    
    even_squares = [x**2 for x in nums if x % 2 == 0]
    even_squares_dict = {x: x**2 for x in nums if x % 2 == 0}
    even_squares_set = {x**2 for x in nums if x % 2 == 0}
    

28. 컴프리헨션 내부에 제어 하위 식을 세 개 이상 사용하지 말라

1
2
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for arr in matrix for x in arr]

이와같은 경우는 컴프리헨션을 사용하는 것이 바람직하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
my_lists = [
    [[1, 2, 3], [4, 5, 6]],
    [[7, 8, 9], [1, 2, 3]],
    [[4, 5, 6], [7, 8, 9]],
]
flat = [x for sublist1 in my_lists
        for sublist2 in sublist1
        for x in sublist2]

flat = []
for sublist1 in my_lists:
    for sublist2 in sublist1:
        flat.extend(sublist2)

컴프리헨션의 내부 제어식이 세 개 이상되면, 차라리 for문이나 도우미함수를 활용하는게 좋다.


29. 대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}

order = ['나사못', '나비너트', '클립']

def get_batches(count, size):
    return count // size

found = {name: get_batches(stock.get(name, 0), 8)
         for name in order
         if get_batches(stock.get(name, 0), 8)}
found = {name: batches for name in order
         if (batches := get_batches(stock.get(name, 0), 8))}

대입식을 사용함으로서 반복적인 식을 하나의 변수로 간단하게 바꿀 수 있다.
한편, 대입식을 컨프리헨션안에 사용하면, 컨프리헨션의 루프 변수가 누출되지 않는다는 장점이 사라진다.
반복을 줄이는 목적으로만 대입식을 사용하는 것이 가장 바람직하다.


30. 리스트를 반환하기보다는 제너레이터를 사용하라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#1. 리스트 버전
def index_words(text):
    result = []
    if text:
        result.append(0)
    for index, letter in enumerate(text):
        if letter == ' ':
            result.append(index + 1)
    return result
result = index_words(address)

#2. 제너레이터 버전
def index_words_iter(text):
    if text:
        yield 0
    for index, letter in enumerate(text):
        if letter == ' ':
            yield index + 1
result = list(index_words_iter(address))

제너레이터를 활용하면

  1. 코드의 잡음을 줄이고
  2. 입력이 너무 긴 경우 메모리가 다 소진되는 것을 방지할 수 있다.

31. 인자에 대해 이터레이션할 때는 방어적이 돼라

제너레이터로 생성된 이터레이터는 이터레이터가 소진이 되는 경우에 대해 무방비해질 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def normalize(numbers):
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result

class ReadVisits:
    def __init__(self, data_path):
        self.data_path = data_path

    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)

#
visits = ReadVisits(path)
percentages = normalize(visits)
print(percentages)

이를 해결하기 위해, 위와 같이 이터레이터를 직접 전달하는게 아니라, 제너레이터 컨테이너를 만들어서 sum()함수나 for 루프에서 __iter__를 통해 새로운 이터레이터가 생성되도록 해야한다.
또한 이터레이터와 컨테이너를 구별하기 위해, isinstance를 활용하면 된다.


32. 긴 리스트 컴프리헨션보다는 제너레이터 식을 사용하라

리스트 컴프리헨션의 문제점은 입력 시퀀스와 같은 수의 원소가 들어있는 리스트 인스턴스를 만들어낼 수 있다는 것이다. -> 오버플로우
이를 해결하기 위해, 제너레이터 식을 사용할 수 있다.

1
2
3
it = (len(x) for x in open('my_file.txt'))

roots = ((x, x**0.5) for x in it)

또한 위와 같이 두 제너레이터 식을 합성할 수도 있다.


33. yield from을 사용해 여러 제너레이터를 합성하라

1
2
3
4
5
6
7
8
9
10
11
12
def animate():
    for delta in move(4, 5.0):
        yield delta
    for delta in pause(3):
        yield delta
    for delta in move(2, 3.0):
        yield delta

def animate_composed():
    yield from move(4, 5.0)
    yield from pause(3)
    yield from move(2, 3.0)

위와 같은 방식으로, yield from 을 사용하면, for 루프를 사용하여 제너레이터를 호출하는 것보다, 훨신 더 간단하고, 성능 좋게 제너레이터를 호출할 수 있다.


34. send로 제너레이터에 데이터를 주입라지 말라

35. 제너레이터 안에서 throw로 상태를 변화시키지 말라

36. 이터레이터나 제너레이터를 다룰 때는 itertools를 사용하라

This post is licensed under CC BY 4.0 by the author.

도덕의 계보 요약과 해석

파이썬 코딩의 기술(5) 클래스와 인터페이스