핀아의 저장소 ( •̀ ω •́ )✧
generator(제너레이터) 본문
generator 는 간단하게 설명하면 iterator 를 생성해 주는 function이다. iterator 는 next() 메소드를 이용해 데이터에 순차적으로 접근이 가능한 object 이다.
💡 이터레이터란 next() 함수 호출 시 계속 그다음 값을 반환하는 객체
generator 는 일반적인 함수와 비슷하게 보이지만, 가장 큰 차이 점은 yield 라는 구문일 것이다. 아래는 generator 함수의 예시 구문이다.
def generator(n):
i = 0
while i < n:
yield i
i += 1
일반 함수와의 차이는 yield 외에는 없다. 그렇다면 먼저 yield 구문이 무엇인지 먼저 알아보자.
✅ yield
yield 는 generator 가 일반 함수와 구분되는 가장 핵심적인 부분이다. yield 를 사용함으로서 어떤 차이가 있게 되는지 살펴보자.
먼저, 일반적인 함수의 경우를 생각해보자.
일반적인 함수는 사용이 종료되면 결과값을 호출부로 반환 후 함수 자체를 종료시킨 후 메모리 상에서 클리어 된다.
하지만, yield 를 사용할 경우는 다르다.
generator 함수가 실행 중 yield 를 만날 경우, 해당 함수는 그 상태로 정지 되며, 반환 값을 next() 를 호출한 쪽으로 전달 하게 된다. 이후 해당 함수는 일반적인 경우 처럼 종료되는 것이 아니라 그 상태로 유지되게 된다. 즉, 함수에서 사용된 local 변수나 instruction pointer 등과 같은 함수 내부에서 사용된 데이터들이 메모리에 그대로 유지되는 것이다.
아래 예시를 보자.
def generator(n):
i = 0
while i < n:
yield i
i += 1
for x in generator(5):
print x
0
1
2
3
4
위 구문을 하나하나 살펴보자.
- for 문이 실행되며, 먼저 generator 함수가 호출된다.
- generator 함수는 일반 함수와 동일한 절차로 실행된다.
- 실행 중 while 문 안에서 yield 를 만나게 된다. 그러면 return 과 비슷하게 함수를 호출했던 구문으로 반환하게 된다. 여기서는 첫번재 i 값인 0 을 반환하게 된다. 하지만 반환 하였다고 generator 함수가 종료되는 것이 아니라 그대로 유지한 상태이다.
- x 값에는 yield 에서 전달 된 0 값이 저장된 후 print 된다. 그 후 for 문에 의해 다시 generator 함수가 호출된다.
- 이때는 generator 함수가 처음부터 시작되는게 아니라 yield 이후 구문부터 시작되게 된다. 따라서 i += 1 구문이 실행되고 i 값은 1로 증가한다.
- 아직 while 문 내부이기 때문에 yield 구문을 만나 i 값인 1이 전달 된다.
- x 값은 1을 전달 받고 print 된다. (이후 반복)
✅ generator expression
위와 같은 generator 함수를 좀 더 쉽게 사용할 수 있도록 generator expression 을 제공한다.
list comprehension 과 비슷하지만, [ ] 대신 ( ) 를 사용하면 된다.
아래는 0 ~ 9 사이 정수 중 홀수 를 list 와 generator object 로 생성한 예제이다.
>>> [ i for i in range(0, 10) if i % 2 ]
[1, 3, 5, 7, 9]
>>> ( i for i in range(0, 10) if i % 2 )
<generator object <genexpr> at 0x7f6105d90960>
( ) 를 사용하면 위와 같이 generator object 로 생성됨을 볼 수 있다.
✅ generator를 왜 사용하는가
- memory를 효율적으로 사용할 수 있다.
- list 의 경우 사이즈가 커질 수록 그만큼 메모리 사용량이 늘어나게 된다. 하지만, generator 의 경우는 사이즈가 커진다 해도 차지하는 메모리 사이즈는 동일하다. 이는 list 와 generator의 동작 방식의 차이에 기인한다.
- list 는 list 안에 속한 모든 데이터를 메모리에 적재하기 때문에 list의 크기 만큼 차지하는 메모리 사이즈가 늘어나게 된다. 하지만 generator 의 경우 데이터 값을 한꺼번에 메모리에 적재 하는 것이 아니라 next() 메소드를 통해 차례로 값에 접근할 때마다 메모리에 적재하는 방식이다.
- 따라서 list 의 규모가 큰 값을 다룰 수록 generator의 효율성은 더욱 높아지게 된다.
- Lazy evaluation 즉 계산 결과 값이 필요할 때까지 계산을 늦추는 효과를 볼 수 있다.
def sleep_func(x):
print "sleep..."
time.sleep(1)
return x
위 sleep_func() 함수는 1초간 sleep 을 수행한 후 x 값을 return 하는 함수이다.
만약 위 sleep_func() 함수를 이용해 llist 와 generator 를 생성하면 어떻게 동작할까.
# list 생성
list = [sleep_func(x) for x in range(0, 5)]
for i in list:
print i
<결과>
sleep...
sleep...
sleep...
sleep...
sleep...
0
1
2
3
4
# generator 생성
gen = (sleep_func(x) for x in range(0, 5))
for i in gen:
print i
<결과>
sleep...
0
sleep...
1
sleep...
2
sleep...
3
sleep...
4
위 결과 값을 보면, generator 를 사용하였을 경우 어떤 차이가 있는지 알 수 있다. list의 경우 list comprehension을 수행 할 때, list의 모든 값을 먼저 수행하기 때문에 sleep_func() 함수를 range() 값 만큼 한번에 수행하게 된다. 만약 sleep_func() 에서 수행하는 시간이 길거나 list 값이 매우 큰 경우 처음 수행 할때 그만큼 부담으로 작용된다.
하지만 generator 의 경우 generator 를 생성할 때는 실제 값을 로딩하지 않고, for 문이 수행 될때 하나씩 sleep_func()을 수행하며 값을 불러오게 된다. 수행 시간이 긴 연산을 필요한 순간까지 늦출 수 있다는 점이 특징이다.
🎃 참고
https://bluese05.tistory.com/56
python generator(제너레이터) 란 무엇인가
Python Generator 먼저 python docs 의 generator 에 대한 정의를 보자. generator A function which returns an iterator. It looks like a normal function except that it contains yield statements for producing a series of values usable in a for-loop or
bluese05.tistory.com
'Computer Science > Python' 카테고리의 다른 글
클래스와 모듈 (0) | 2023.05.16 |
---|