제너레이터

이 노트북은 제이크 반더플라스(Jake VanderPlas)의 A Whirlwind Tour of Python(OReilly Media, 2016)를 기반으로 만들어졌습니다. 이 내용은 CC0 라이센스를 따릅니다. 전체 노트북의 목록은 https://github.com/rickiepark/WhirlwindTourOfPython 에서 볼 수 있습니다.

< 리스트 내포 | 목차 | 모듈과 패키지 >

 

여기에서 제너레이터 표현식제너레이터 함수를 포함하여 파이썬 제너레이터를 자세히 배워 보겠습니다.

제너레이터 표현식

리스트 내포와 제너레이터 표현식의 차이는 이따금 헷갈립니다. 이 둘의 차이를 간단히 살펴 보겠습니다:

리스트 내포는 대괄호를 사용하고 제너레이터 표현식은 소괄호를 사용합니다

다음은 대표적인 리스트 내포입니다:

[n ** 2 for n in range(12)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

반면 다음은 대표적인 제너레이터 표현식입니다:

(n ** 2 for n in range(12))
<pre>&lt;generator object  at 0x103274990&gt;</pre>

제너레이터 표현식을 출력하면 내용이 출력되지 않습니다. 제너레이터 표현식의 내용을 출력하는 방법은 list 생성 함수에 전달하는 것입니다:

G = (n ** 2 for n in range(12))
list(G)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

리스트는 값들의 모음이고, 제너레이터는 값을 만들어내는 한 방식입니다

리스트를 만들 때 실제 일련의 어떤 값들을 만드므로 연관되어 일정량의 메모리가 소모됩니다. 제너레이터를 만들 때는 일련의 값들을 만들지 않고 이런 값들을 만드는 방법을 만듭니다. 둘 다 같은 반복자 인터페이스에 사용할 수 있습니다:

L = [n ** 2 for n in range(12)]
for val in L:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 
G = (n ** 2 for n in range(12))
for val in G:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 

차이는 제너레이터 표현식은 필요할 때까지 실제로 값을 계산하지 않는다는 점입니다. 메모리를 효율적으로 사용할 수 있을 뿐만 아니라 계산 비용도 절감할 수 있습니다! 리스트의 크기는 가용 메모리 범위로 제한되지만 제너레이터의 크기는 제한이 없다는 뜻이 됩니다!

무한 제너레이터 표현식의 한 예는 itertools에 정의된 count 반복자를 사용하여 만들 수 있습니다:

from itertools import count
count()
count(0)
for i in count():
    print(i, end=' ')
    if i >= 10: break
0 1 2 3 4 5 6 7 8 9 10 

count 반복자는 멈출 때까지 영원히 카운팅할 것입니다. 영원히 실행되는 제너레이터를 만드는 것도 쉽습니다:

factors = [2, 3, 5, 7]
G = (i for i in count() if all(i % n > 0 for n in factors))
for val in G:
    print(val, end=' ')
    if val > 40: break
1 11 13 17 19 23 29 31 37 41 

적절히 factors 리스트를 확장히키면 에라토스테네스의 체 알고리즘을 사용하여 소수 제너레이터를 구성할 수 있습니다. 조금 후에 이에 대해 더 자세히 알아 보겠습니다.

리스트는 여러번 반복할 수 있지만 제너레이터 표현식은 한번만 사용됩니다

이것이 제너레이터 표현식의 장점 중 하나입니다. 리스트를 사용하면 다음과 같이 쉽게 할 수 있습니다:

L = [n ** 2 for n in range(12)]
for val in L:
    print(val, end=' ')
print()

for val in L:
    print(val, end=' ')
0 1 4 9 16 25 36 49 64 81 100 121 
0 1 4 9 16 25 36 49 64 81 100 121 

하지만 제너레이터 표현식은 한 번 반복하면 사라집니다:

G = (n ** 2 for n in range(12))
list(G)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
list(G)
[]

이런 기능 덕택에 반복이 중지되고 다시 시작될 수 있기 때문에 매우 유용합니다:

G = (n**2 for n in range(12))
for n in G:
    print(n, end=' ')
    if n > 30: break

print("\n그 사이에 무언가 다른 일을 합니다")

for n in G:
    print(n, end=' ')
0 1 4 9 16 25 36 
그 사이에 무언가 다른 일을 합니다
49 64 81 100 121 

디스크에 있는 데이터 파일들을 다룰 때 매우 유용합니다. 배치 처리를 할 때 제너레이터가 처리한 파일들을 기억할 수 있습니다.

제너레이터 함수: yield를 사용

이전 절에서 리스트 내포는 비교적 간단한 리스트를 만드는데 좋고 보통의 for 루프는 아주 복잡한 상황에 더 잘 맞습니다. 제너레이터 표현도 동일합니다. yield 문을 사용하는 제너레이터 함수를 사용하여 더 복잡한 제너레이터를 만들 수 있습니다.

동일한 리스트를 만드는 두 가지 방법이 다음에 나타나 있습니다:

L1 = [n ** 2 for n in range(12)]

L2 = []
for n in range(12):
    L2.append(n ** 2)

print(L1)
print(L2)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

비슷하게 동일한 제너레이터를 만드는 두 가지 방법이 있습니다:

G1 = (n ** 2 for n in range(12))

def gen():
    for n in range(12):
        yield n ** 2

G2 = gen()
print(*G1)
print(*G2)
0 1 4 9 16 25 36 49 64 81 100 121
0 1 4 9 16 25 36 49 64 81 100 121

제너레이터 함수는 한 번 값을 리턴하는 return 대신 (무한도 가능한) 시퀀스를 반환하는 yield를 사용한 함수입니다.
제너레이터 표현식과 마찬가지로 제너레이터의 상태는 부분 반복 간에 유지됩니다. 새로운 제너레이터를 얻고 싶으면 간단히 함수를 다시 호출하면 됩니다.

예제: 소수 제너레이터

다음 제가 제일 좋아하는 제너레이터 함수의 예를 보겠습니다. 무한한 소수를 생성하는 함수입니다. 전통적인 알고리즘은 다음과 같이 작동하는 에라토스테네스의 체입니다:

# 후보 리스트를 생성합니다
L = [n for n in range(2, 40)]
print(L)
[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]
# 첫 번째 값의 모든 배수를 제거합니다
L = [n for n in L if n == L[0] or n % L[0] > 0]
print(L)
[2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39]
# 두 번째 값의 모든 배수를 제거합니다
L = [n for n in L if n == L[1] or n % L[1] > 0]
print(L)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37]
# 세 번째 값의 모든 배수를 제거합니다
L = [n for n in L if n == L[2] or n % L[2] > 0]
print(L)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

아주 충분히 큰 리스트에서 충분한 시간동안 이를 반복하면 원하는 만큼의 소수를 생성할 수 있습니다.

이 로직을 제러레이터 함수에 캡슐화해 보죠:

def gen_primes(N):
    """N까지의 소수를 생성합니다"""
    primes = set()
    for n in range(2, N):
        if all(n % p > 0 for p in primes):
            primes.add(n)
            yield n

print(*gen_primes(100))
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

이것이 전부입니다! 계산이 더 효율적인 에라토스테네스의 체의 구현은 아니지만 제너레이터 함수 문법이 복잡한 시퀀스를 만드는데 얼마나 편리한지 보여줍니다.

 

< 리스트 내포 | 목차 | 모듈과 패키지 >

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.