Generator

이성준·2023년 6월 26일
0

다시한번 느끼는 거지만 파이썬은 파도 파도 끝이 안보인다,,
일단, 시작해보겠다. Generator는 pytorch로 딥러닝 모델을 돌릴때 Dataloader의 형식이 Generator였다.
처음 데이터 구성을 해볼때 next()를 사용해서 데이터를 살펴보곤 했는데, 이제 본격적으로 한번 알아보겠다.

Generator(제너레이터)
Generator를 책에서는 다음과 같이 설명한다.
"제너레이터는 루프의 반복 동작을 제어할 수 있는 루틴 형태를 말한다." - [파이썬 알고리즘 인터뷰]

하지만, 이말은 잘 와닫지 않는다. 찾아보면
"Generator는 Iterator를 생성해주는 함수이다." 라는 설명이 더 와닫는다.

잠깐 Iterator
Iterator는 상태를 유지하며(순회하고 있는 위치를 기억하며) 반환할 수 있는 마지막 값까지 원소를 필요할 때마다 하나씩 반환하는 것이라고 생각하면 된다. 자체적으로 내장하고 있는 next 메소드를 통해 다음 값을 가져올 수 있다.

  • list와 tuple등은 Iterator라고 하지 않고 Iterable하다고 한다.
  • 이때 유의할점은 Iterable한 객체는 iter()함수를 통해서 Iterator로 만들 수 있다는 것이다.
  • for문은 내부적으로 Iterator를 생성하여서 동작한다. __iter__ __next__ 를 이용해서 순회

자 그렇다면, 직접 Iterator 객체를 만들고자 해보자. 그렇다면, Iterator객체를 만들기 위해서,
__iter__(Iterator객체를 반환하는 함수,iter()함수를 이용해서 구현) &
__next__(다음 value를 returns하는 함수)등의 메서드를 구현해야 한다. (이 말이 의미하는 바는 위에 메소드들을 구현함으로써 우리만의 customized iterator를 만들 수 있다는 것이다. 즉, next의 condition을 조절해서 우리가 원하는 값만 뽑아낼수도 있다.)

iterable한 객체를 iterator로 만들 수 있고, 이 iterator객체를 여러개 만들면, 각각은 서로 다른 상태(위치)를 기억하면서 순회할 수 있다는 장점이 있다.

하지만 Generator는 이러한 복잡한 Iterator 구성과정을 단축시켜 두었다.

그렇다면 이러한 Generator를 만드려면 어떻게 해야할까?

→ 바로, 함수내부에서 yield 구문을 사용하여 제너레이터를 만든다.

기존의 함수는 return구문을 만나면 값을 리턴하고 모든 함수의 동작을 종료하는데, yield구문을 실행하면 그때까지 실행중이던 값을 순서를 기억해서 내보낸다.
함수가 끝난이후에는 순서를 기억해 Iterator가 생성되며 이를 next()를 이용해서 출력할 수 있다.

def make_generator():
    n=0
    while n<100:
        yield n
        n += 1


def main():
    gen = make_generator()
    print(type(gen))
    print(next(gen))
    print(next(gen))
    print(next(gen))


main()

활용예시 - range()

제너레이터 방식을 활용하는 대표적인 함수로 range()가 있다.
print(range(1,3))을 찍어보면

위와 같이 나오는데, 여기서 range클래스는 제너레이터 역할을 수행한다.

이때, 제너레이터가
range(0,1000000)[n for n in range(1000000)] 둘의 차이점은 뭘까?
오른쪽은 실제로 백만까지의 숫자를 메모리에 할당하였고
왼쪽은 100만까지 생성해야 한다는 조건만 존재한다.

이는 실제로 얼마나 사용하냐에 따라서 차이가 더욱 커지는데 가령 5개의 숫자를 뽑아쓰려고 한다고 생각해보자.

오른쪽은 확정적으로 메모리가 100만개의 숫자를 저장할만큼 필요하고, 왼쪽은 필요한 숫자만 생성해서 쓰면 된다.

게다가 미리 생성하지 않은 값은 인덱스에 접근이 안될거라고 생각할 수 있지만 인덱스로 접근 시에는 바로 생성하도록 구현돼있기 때문에 리스트와 거의 동일한 느낌으로 사용가능하다.

아래의 예시를 보자.

import sys
A = range(1,1000000)
B = [n for n in range(1000000)]
print("A의 크기:", sys.getsizeof(A))
print("B의 크기:", sys.getsizeof(B))
print("5번째 B의 Element:", B[5])

  • Caution !

일반적인 generator및 Iterator는 Indexing이 허용되지 않는다 range class의 경우에만 허용된다.

So in Python 3.x, the range() function got its own type. In basic terms, if you want to use range() in a for loop, then you're good to go. However you can't use it purely as a list object. For example you cannot slice a range type.
-> range class가 반환하는 것이 Iterator가 아닌 range class임을 명심하자

출처
내블로그를 보고 이해가 가지 않는다면 위의 블로그를 보자. 아주 상세히 매우 잘 설명돼있다.
위의 출처에서 언급한 내용중 가장 기가 막힌 내용이 iterable, iterator, generator의 상속관계를 따진 것이다. 결과는 다음과 같다

Iterator는 Iterable의 자식 클래스다.
Generator는 Iterator의 자식 클래스다.
따라서, Generator는 Iterable의 자식 클래스다.


P.S 파이썬은 알아가면 알아갈수록 모든것이 Class로 구성돼있음을 절감한다.
확실히 사용자가 편하게 사용할 수 있도록 구성해놓은게 느껴진다.
얼른 magic method 포스팅을 작성해서 클래스와 더욱 친해져보겠다.

profile
Time-Series

0개의 댓글