🐍Python | Django

[Python] generator

이줭 2022. 5. 9. 21:50
728x90

generator를 간단히 설명하면, iterator를 생성해주는 function이다.

 

※ iterator?

https://dev-jy.tistory.com/20

 

[Python] iterable? iterator?

iterable? iterator? iterable의 의미는 요소를 하나씩 차례로 반환 가능한 object를 뜻한다. iterable의 예로는 sequence type인 list, str, tuple 등이 있고, non-sequence type인 dict나 file도 iterable 하다..

dev-jy.tistory.com

아래의 예시를 보자.

def generator(n):
    i = 0
    while i < n:
        yield i
        i += 1

얼핏 보면 일반 함수와 비슷해 보이지만, yield라는 키워드가 가장 큰 차이점일 것이다.

 

yield는 무엇일까?🤔

 

yield는 일반 함수와 generator가 구분되는 가장 큰 차이점으로써, 일반 함수는 사용이 종료되면 결과값을 호출한 곳으로 반환하고 함수를 종료시킨 후 메모리에서 제거된다. 하지만, yield를 사용하게 되면 해당 함수는 그 상태로 정지되며, 반환 값을 next()를 호출한 쪽으로 전달하게 된다. 이후에 메모리에서 제거되지 않고 그 상태로 유지되며, 함수 내부에서 사용한 데이터들도 메모리에 그대로 유지된다.

 

위의 함수를 호출해보자.

def generator(n):
    i = 0
    while i < n:
        yield i
        i += 1

for i in generator(5):
    print(i)
    
# 0
# 1
# 2
# 3
# 4

for문이 실행되면 generator함수가 호출되고 첫번째 i 값인 0을 반환한다. 이후 함수가 종료되는 것이 아니라 그대로 유지되며 다시 for문에 의해 generator함수가 호출되면 generator 함수가 처음부터 시작되는 게 아니라 yield이후 구문부터 시작하게 된다.

 

따라서 i += 1이 실행되고, i값이 1로 증가된다. (이후, while문이 끝날 때까지 반복)

 

 

파이썬에서는 generator를 좀 더 쉽게 사용할 수 있도록 expression을 제공한다. list comprehension과 비슷하지만, []대신 ()를 사용한다.

>>> (i for i in range(10) if i % 2)
<generator object <genexpr> at 0x7f105d90905>

()를 사용하면 위와 같이 generator object가 생성되는 것을 확인할 수 있다.

 

그래서 generator를 쓰면 뭐가 좋아요? 🙋‍♂️

 

먼저, generator를 사용하면 메모리를 효과적으로 사용할 수 있다. list의 경우 사이즈가 커질수록 메모리 사용량이 늘어나게 된다. 하지만 generator의 경우 사이즈가 커진다 해도 차지하는 메모리 사이즈는 동일하다.

 

list는 list안의 데이터를 모두 메모리에 적재하기 때문에 크기만큼 차지하는 메모리 사이즈가 늘어나게 되지만, generator의 경우 데이터 값을 한 번에 메모리에 적재하는 것이 아니라, next() 메서드를 통해 차례로 값에 접근할 때마다 메모리에 적재하기 때문에 사이즈와 상관없이 동일한 메모리 사이즈를 차지하게 되는 것이다.

 

다음으로 Lazy Evaluation 효과를 볼 수 있다.

다음 sleep 함수를 이용해 list와 generator를 생성하면 어떻게 동작하게 될까?

def sleep(x):
    print('sleep....')
    time.sleep(1)
    return x
    
list = [sleep(i) for i in range(5)]
generator = (sleep(i) for i in range(5))

for i in list:
    print(i)
    
for i in generator:
    print(i)

아래의 결과를 보면 list의 경우 list comprehension이 수행될 때, list의 모든 값을 먼저 수행하기 때문에 sleep함수를 range의 값만큼 한 번에 수행하게 된다.

 

만약 sleep()에서 수행하는 시간이 실거나 list값이 매우 큰 경우 처음 수행 시 부담으로 작용하게 된다. 반면, generator의 경우 생성 시에는 실제 값을 로딩하지 않고, for문이 수행될 때 sleep()을 수행하며 값을 하나씩 불러오게 된다.

수행 시간이 긴 연산을 필요시까지 늦출 수 있다는 것이다.

# 결과

# list
sleep....
sleep....
sleep....
sleep....
sleep....
0
1
2
3
4

# generator
sleep....
0
sleep....
1
sleep....
2
sleep....
3
sleep....
4

따라서, 데이터가 무제한이어서 모든 데이터를 리턴할 수 없는 경우나 대량의 데이터를 일부씩 처리하는 것이 필요한 경우 또는 모든 데이터를 미리 계산하면 속도가 느려 그때그때 계산해야 하는 경우 등에 종종 generator가 사용된다.

 

참고 : https://bluese05.tistory.com/56

728x90

'🐍Python | Django' 카테고리의 다른 글

[Django] QuerySet method  (0) 2022.05.12
[Django] QuerySet?!  (0) 2022.05.12
[Python] iterable? iterator?  (0) 2022.05.08
[Python] GIL  (0) 2022.05.07
[Python] Python의 메모리 관리  (0) 2022.05.03