파이썬은 itertools, functools 모듈과 내장 클래스를 통해 이터러블을 순환하는 다양한 방법을 제공합니다. 이 중 개인적으로 많이 사용하는 기능을 모아 정리했습니다. itertools, functools은 이 글에 있는 것보다 더 많은 기능을 제공합니다. 또한 이 글에서 소개할 enuermate, map, filter, zip, product은 이터레이터 입니다. 그리고 tee함수의 반환값 또한 이터레이터 입니다. 즉 재사용이 불가능 합니다. reduce는 이터러블이 아니지만 이터러블을 순환하며 특정 계산을 해줍니다.

1. enumerate, map, filter

이터러블을 순환할 때 몇번째 순환값인지 알고 싶을 때 enumerate 를 사용합니다. enumerate 는 이터러블의 순환값과 몇번째 값인지를 튜플로 묶어 반환합니다. 이터러블이 리스트라면 이 값은 인덱스입니다. map 은 이터러블의 순환값을 변경해 줍니다. map은 변경된 순환값을 반환하는 함수를 인자로 받습니다. filter 는 이터러블을 순환할지 아닐지 선별할 수 있습니다. true or false 값을 반환하는 함수를 인자로 받습니다. 아래는 예시입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
print('enumerate')
for i, num in enumerate('abc'):
    print(i, num)

print('map')
for num in map(lambda x: x * 10, range(3)):
    print(num)

print('filter')
for num in filter(lambda x: x != 1, range(3)):
    print(num)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enumerate
0, 'a'
1, 'b'
2, 'c'
map
0
10
20
filter
0
2

2. zip, product

zip 은 여러 이터러블을 동시에 순환합니다. 모든 이터러블로 부터 이터레이터를 얻습니다. 그후, zip__next__ 메소드가 호출시 등록된 모든 이터레이터에 대해 __next__ 메소드를 호출합니다. 그렇게 얻은 값은 튜플로 묶어 반환합니다. zip을 생성시 전달값의 앞쪽에 있던 이터러블의 __next__ 메소드를 먼저 호출합니다.

1
2
3
4
iter1 = range(3)
iter2 = 'abc'
for a, b in zip(iter1, iter2):
    print(a, b)
1
2
3
0, 'a'
1, 'b'
2, 'c'

itertools 의 productzip과 반대로 순차적으로 순환합니다. 전달값 뒤쪽의 이터레이터를 먼저 순환합니다. 다중 반복문과 같은 기능을 하지만 약간 다릅니다. 다중반복문은 안쪽 반복문의 이터러블이 이터레이터일 경우 안쪽 반복문이 한번 순환다음부터 StopIteration 을 일으킵니다. 즉 안쪽반복문을 여러번 반복할 수 없습니다. 하지만 product는 이터러블을 미리순환하여 값을 기억해 놓습니다. 그리고 미리 얻은 값을 순환합니다. 따라서 product의 전달값으로 이터레이터를 사용해도 됩니다.

1
2
3
4
iter1 = range(3)
iter2 = 'abc3'
for a, b in product(iter1, iter2):
    print(a, b)
1
2
3
4
5
6
7
8
9
0, 'a'
0, 'b'
0, 'c'
1, 'a'
1, 'b'
1, 'c'
2, 'a'
2, 'b'
2, 'c'

3. tee

이터레이터는 재사용이 불가능 합니다. 두 번 사용하고 싶다면 같은 내용의 이터레이터기 두 개 필요합니다. 그러나 한 이터레이터에서 나온 값을 기억한 후, 다른 이터레이터가 값을 요구할 때 미리 기억한 값을 주면 안될까요. 이러한 방식으로 여러 개의 이터레이터가 하나의 이터레이터에서 값을 구해오며 계산량이 줄어듭니다. itertools 의 tee 함수가 이러한 기능을 제공합니다. tee 함수는 이터러블에서 이러레이터를 얻은후, 그 이터레이터에서 값을 얻어오는 여러 개의 이터레이터를 만듭니다. 사용법은 다음과 같습니다. 두번째 인자는 만들 이터레이터의 수 입니다.

1
	iter1, ter2, ter3 ... = tee(iterable, n)

이렇게 만들어진 이터레이터는 미리 얻어온 값이 있으면 기억한 값을, 얻어온 값이 없으면 원본 이터레이터를 순환시킵니다. 아래는 사용 예시입니다.

1
2
3
4
it1, it2 = tee(range(5), 2)
current = next(it1)
for current, prev in zip(it1, it2):
    print(current, prev)
1
2
3
4
0, 1
1, 2
2, 3
3, 4

4. reduce

functools 모듈의 reduce 함수는 이터러블을 만들어 주지 않습니다. 대신 이터러블을 순환하며 순환값을 모두 더해주거나 곱해줄 수 있습니다. reduce는 아래와 같이 sum 이나 product 같은 함수의 구현하기 좋습니다. 비슷한 기능을 하는 이터레이터가 필요하다면 itertools 의 accumulate 가 있습니다.

1
2
3
4
num = 5
sum = reduce(lambda s, x: s + x, range(num+1), 0)
product = reduce(lambda s, x: s * x, range(1, num+1), 1)
print(sum, product)
1
15, 120

A. Tee 함수 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class MyTee_:
    
    def __init__(self, it, memory, counts, tee_count):
        self.index     = -1
        self.it        = it
        self.memory    = memory
        self.counts    = counts
        self.tee_count = tee_count
        self.tees      = None
        
    def __next__(self):
        
        memory = self.memory
        counts = self.countsㄴ        it = self.it
        
        self.index += 1
        value, count = None, None
        if len(memory) <= self.index:
            value = next(it)
            memory.append(value)
            counts.append(1)
        else:
            value = memory[self.index]
            count = counts[self.index] + 1
            counts[self.index] = count
            
        if count == self.tee_count:
            memory.pop(0)
            counts.pop(0)
            for tee in self.tees: tee.index -= 1
            
        return value
        
    def __iter__(self):
        return self
    
  	  
def MyTee(iterable, n):
    it     = iter(iterable)
    memory = []
    counts = []
    tees   = [MyTee_(it, memory, counts, n) for i in range(n)]
    for tee in tees: tee.tees = tees
    return tuple(tees)


a, b, c = MyTee([1,2,3,4,5,6], 3)
print(next(a))
print(next(a))
print(next(b))
print([*zip(a,b,c)])
print(next(b))
print(next(c))
print(next(c))
1
2
3
4
5
6
7
1
2
1
[(3, 2, 1), (4, 3, 2), (5, 4, 3), (6, 5, 4)]
6
5
6
1
2
3
4
5
6
7
8
9
from itertools import tee
a, b, c = tee([1,2,3,4,5,6], 3)
print(next(a))
print(next(a))
print(next(b))
print([*zip(a,b,c)])
print(next(b))
print(next(c))
print(next(c))
1
2
3
4
5
6
7
1
2
1
[(3, 2, 1), (4, 3, 2), (5, 4, 3), (6, 5, 4)]
6
5
6