본문 바로가기
기타

TIL - 22.02.03

by DGK 2022. 2. 3.

 

개인 공부 후 자료를 남기기 위한 목적이므로 내용 상에 오류가 있을 수 있습니다.

 

2월 3일(목)


generators

generator란?

 

  1. generator는 iterator를 생성해주는 function이다.
    (iterator는 next() 메소드로 데이터에 순차적으로 접근할 수 있는 객체임)
  2. generator는 일반적인 함수와 비슷해 보이지만, 가장 큰 차이점은 yield를 사용하는 것이다.
  3. generator 함수가 실행 중에 yield를 만나면, 해당 함수는 그 상태로 정지되며 반환 값을 next()가 호출한 쪽으로 전달하게 된다. 이후 해당 함수는 일반적인 경우처럼 종료되는 것이 아니라 그 상태로 유지된다. 즉, 함수에서 사용된 local 변수나 instruction pointer 등과 같은 함수 내부에서 사용된 데이터들이 메모리에 그대로 유지되는 것이다.
  4. 하지만, 일반함수의 경우에는 사용이 종료되면 결과 값을 호출부로 반환 후에 함수 자체를 종료시키고 메모리 상에서도 클리어 된다.

 

(참고)

def generator(n):
    i = 0

    while i < n:
        yield i
        i += 1

for x in generator(5):
    print(x)

'''
출력 :
0
1
2
3
4

'''

 

위 코드를 하나씩 살펴보면 다음과 같다.

 

  1. for문이 실행되며 먼저 generator 함수가 호출된다.
  2. generator 함수는 일반 함수와 동일한 절차로 실행된다.
  3. generator 함수가 실행되면서 while문 안에 있는 yield를 만나면, 일반 함수의 return과 비슷하게 함수를 호출했던 구문으로 반환하게 된다. 하지만 일반 함수의 return과 달리 generator 함수가 종료되는 것이 아니라 그대로 유지된다.
  4. 첫 번째로 x 값에는 yield에서 전달된 0 값이 저장된 후 print문이 실행된다. 그 후에 for문에 의해 다시 generator 함수가 호출된다.
  5. 이 때 generator 함수가 처음부터 다시 시작되는 것이 아니라 yield 이후의 구문부터 시작된다. 따라서 i += 1 구문이 실행되고 i 값은 1로 증가한다.
  6. 그리고 while 문의 yield를 만나면 다시 x 값에 i 값인 1이 전달된다.
  7. 이후에도 앞의 원리와 동일하게 x 값은 1을 전달받고 print문으로 출력된다. (이후 동일반복)

 

 

generator expression

generator expression이란?

 

  1. generator expression은 generator 함수를 보다 쉽게 사용할 수 있도록 도와준다.
  2. 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를 사용하는 이유

 

  1. generator를 사용하면 memory를 효율적으로 사용할 수 있는 이점이 있다.
  2. 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

'''

 

위의 코드로 다음과 같은 사실을 알 수 있다.

 

  1. list의 경우 사이즈가 커질수록 메모리 사용량이 늘어나지만, generator의 경우에는 사이즈에 상관없이 차지하는 메모리 사용량은 동일하다.
  2. 이는 list와 generator의 동작방식의 차이가 있기 때문이다.
  3. list는 list 안에 속하는 모든 데이터를 메모리에 적재하기 때문에 list의 크기만큼 메모리를 사용한다.
  4. 하지만 generator는 데이터 값을 한꺼번에 메모리에 적재하는 것이 아니라, next() 메소드를 통해 차례대로 값에 접근할 때마다 메모리에 적재하는 방식을 사용한다.
  5. 따라서 규모가 큰 데이터를 다룰수록 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

'''

 

위의 코드로 다음과 같은 사실을 알 수 있다.

 

  1. list의 경우에는 list comprehension을 실행할 때, list의 모든 값들을 먼저 실행하기 때문에 sleep_func() 함수를 range() 값 만큼 한 번에 실행하게 된다. 
  2. 만약, sleep_func()에서 실행되는 시간이 길거나 list 값이 매우 큰 경우라면, 해당 코드를 처음 실행할 때 상당한 부담으로 작용한다.
  3. 반면에 generator의 경우에는 generator expression을 실행할 때, 실제 값을 로딩하지 않고 for문이 실행될 때 하나씩 sleep_func() 함수를 실행하며 해당 값을 불러온다.
  4. 이러한 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

댓글