- 책 파이썬 코딩의 기술을 읽고 정리한 내용입니다. —
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))
제너레이터를 활용하면
- 코드의 잡음을 줄이고
- 입력이 너무 긴 경우 메모리가 다 소진되는 것을 방지할 수 있다.
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 루프를 사용하여 제너레이터를 호출하는 것보다, 훨신 더 간단하고, 성능 좋게 제너레이터를 호출할 수 있다.