개인 공부 후 자료를 남기기 위한 목적이므로 내용 상에 오류가 있을 수 있습니다.
2월 3일(목)
generators
generator란?
- generator는 iterator를 생성해주는 function이다.
(iterator는 next() 메소드로 데이터에 순차적으로 접근할 수 있는 객체임) - generator는 일반적인 함수와 비슷해 보이지만, 가장 큰 차이점은 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 함수는 일반 함수와 동일한 절차로 실행된다.
- generator 함수가 실행되면서 while문 안에 있는 yield를 만나면, 일반 함수의 return과 비슷하게 함수를 호출했던 구문으로 반환하게 된다. 하지만 일반 함수의 return과 달리 generator 함수가 종료되는 것이 아니라 그대로 유지된다.
- 첫 번째로 x 값에는 yield에서 전달된 0 값이 저장된 후 print문이 실행된다. 그 후에 for문에 의해 다시 generator 함수가 호출된다.
- 이 때 generator 함수가 처음부터 다시 시작되는 것이 아니라 yield 이후의 구문부터 시작된다. 따라서 i += 1 구문이 실행되고 i 값은 1로 증가한다.
- 그리고 while 문의 yield를 만나면 다시 x 값에 i 값인 1이 전달된다.
- 이후에도 앞의 원리와 동일하게 x 값은 1을 전달받고 print문으로 출력된다. (이후 동일반복)
generator expression
generator expression이란?
- generator expression은 generator 함수를 보다 쉽게 사용할 수 있도록 도와준다.
- generator expression은 list comprehension과 비슷하지만, [] 괄호 대신 () 괄호를 사용한다.
(참고)
list_comprehension = [i for i in range(10) if i % 2]
print(list_comprehension)
# 출력 : [1, 3, 5, 7, 9]
gen_expression = (i for i in range(10) if i % 2)
for j in gen_expression:
print(j, end=' ')
# 출력 : 1 3 5 7 9 %
generator expression을 통해 generator를 사용하는 이유
- generator를 사용하면 memory를 효율적으로 사용할 수 있는 이점이 있다.
- Lazy evaluation 효과(계산 결과 값이 필요할 때까지 계산을 늦추는 효과)를 볼 수 있다.
(참고1 : memory의 효율적 사용)
import sys
print(sys.getsizeof([i for i in range(100) if i % 2])) # list comprehension
print(sys.getsizeof([i for i in range(1000) if i % 2])) # list comprehension
print(sys.getsizeof((i for i in range(100) if i % 2))) # generator expression
print(sys.getsizeof((i for i in range(1000) if i % 2))) # generator expression
'''
출력 :
472
4216
104
104
'''
위의 코드로 다음과 같은 사실을 알 수 있다.
- list의 경우 사이즈가 커질수록 메모리 사용량이 늘어나지만, generator의 경우에는 사이즈에 상관없이 차지하는 메모리 사용량은 동일하다.
- 이는 list와 generator의 동작방식의 차이가 있기 때문이다.
- list는 list 안에 속하는 모든 데이터를 메모리에 적재하기 때문에 list의 크기만큼 메모리를 사용한다.
- 하지만 generator는 데이터 값을 한꺼번에 메모리에 적재하는 것이 아니라, next() 메소드를 통해 차례대로 값에 접근할 때마다 메모리에 적재하는 방식을 사용한다.
- 따라서 규모가 큰 데이터를 다룰수록 list를 사용하는 것보다 generator(expression)를 사용하는 것이 더욱 효율적이다.
(참고2 : Lazy evaluation 효과)
import time
def sleep_func(x):
print("sleep...")
time.sleep(1)
return x
# list comprehension
list = [sleep_func(x) for x in range(5)]
for i in list:
print(i)
#generator expression
gen = (sleep_func(x) for x in range(5))
for i in gen:
print(i)
'''
<list comprehension>
출력 :
sleep...
sleep...
sleep...
sleep...
sleep...
0
1
2
3
4
'''
'''
<generator expression>
출력 :
sleep...
0
sleep...
1
sleep...
2
sleep...
3
sleep...
4
'''
위의 코드로 다음과 같은 사실을 알 수 있다.
- list의 경우에는 list comprehension을 실행할 때, list의 모든 값들을 먼저 실행하기 때문에 sleep_func() 함수를 range() 값 만큼 한 번에 실행하게 된다.
- 만약, sleep_func()에서 실행되는 시간이 길거나 list 값이 매우 큰 경우라면, 해당 코드를 처음 실행할 때 상당한 부담으로 작용한다.
- 반면에 generator의 경우에는 generator expression을 실행할 때, 실제 값을 로딩하지 않고 for문이 실행될 때 하나씩 sleep_func() 함수를 실행하며 해당 값을 불러온다.
- 이러한 generator의 특징을 Lazy evaluation이라고 하며, 이는 실행시간이 긴 연산을 필요한 순간까지 늦출 수 있는 특징을 가진다. (참고로, Lazy evaluation의 특징을 활용하면 제한된 시간과 메모리 안에서도 실제로 사용되는 부분만 지연평가를 하기 때문에 무한의 자료구조 구현이 가능함)
'기타' 카테고리의 다른 글
TIL - 22.02.07 (0) | 2022.02.07 |
---|---|
TIL - 22.02.04 (0) | 2022.02.04 |
TIL - 22.02.02 (0) | 2022.02.02 |
TIL - 22.02.01 (0) | 2022.02.02 |
TIL - 22.01.31 (0) | 2022.01.31 |
댓글