(그냥 현재 심경 🤡)
generator는 간단하고도 강력한 iterator를 생성해주는 함수이다. generator는 일반 함수처럼 작성되지만 데이터를 반환시키고 싶을 때 yield
문을 사용한다. generator는 모든 데이터값과 어떤 statement가 마지막으로 실행되었는지 기억하고 next()
가 호출될 때마다 yield
호출 후 현재의 상태에서 머물고 있던 마지막 부분에서 다시 시작한다. 다음의 예제가 generator를 생성하는게 얼마나 쉬운지 보여준다.
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
for char in reverse('gold'):
print(char)
>>>
d
l
o
g
generator로 할 수 있는 어떤것이든 iterators로도 할 수 있다. generator가 가지는 장점은 __iter__()
와 __next__()
메소드가 자동으로 생성된다는 점이다.
print(reverse('data'))
print(dir(reverse('data')))
>>>
<generator object reverse at 0x7fe1a7175190>
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
그리고 또 하나의 주요 기능은 지역 변수들과 실행 상태가 호출 간에 자동으로 보관된다는 점이다. 이것은 self.index
나 self.data
와 같은 인스턴스 변수를 사용하는 접근법에 비교해 함수를 쓰기 쉽고 명료하게 만든다.
자동 메소드 생성과 프로그램 상태의 저장에 더해, 제너레이터가 종료할 때 자동으로 StopIteration
에러를 띄운다. 조합하면, 이 기능들이 일반 함수를 작성하는 것만큼 이터레이터를 만들기 쉽게 만든다.
간단한 제너레이터는 리스트 컴프리헨션과 비슷하지만, [대괄호]
대신, (괄호)
를 사용하는 문법을 사용한 표현식으로 간결하게 코딩이 가능하다. 이 표현식들을 둘러싸는 함수가 제너레이터를 즉시 사용하는 상황을 위해 설계되었다. 제너레이터 표현식은 완전한 제너레이터 정의보다 간결하지만, 융통성은 떨어지고, 비슷한 리스트 컴프리헨션보다 메모리를 덜 쓰는 경향이 있다.
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> unique_words = set(word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']
제너레이터 표현식은 Lazy evaluation을 위해서 사용될 수 있다. Lazy evaluation이 무엇인지는 다음 예제를 통해 알아보자.
import time
L = [1, 2, 3]
def print_iter(iter):
for element in iter:
print(element)
def lazy_return(num):
print("sleep 1s")
time.sleep(1)
return num
print("comprehension_list=")
comprehension_list = [lazy_return(i) for i in L]
print_iter(comprehension_list)
print("generator_exp=")
generator_exp = (lazy_return(i) for i in L)
print_iter(generator_exp)
>>>
comprehension_list=
sleep 1s
sleep 1s
sleep 1s
1
2
3
generator_exp=
sleep 1s
1
sleep 1s
2
sleep 1s
3
위의 결과에서 볼 수 있듯이, lazy evaluation은 평가를 늦추고 필요할때 값을 계산한다.
comprehension_list
에서는 미리 객체들을 모두 평가되어진 상태로 갖고 있다가 한번에 출력하였고, generator_exp
에서는 요청이 들어올때만 평가를 하기때문에 정보가 많을수록 메모리상에 많은 이득을 가져다준다.