hyei-devlog

[파이썬 문법 체크] 제너레이터(Generator)와 이터레이터(Iterator) 본문

Python

[파이썬 문법 체크] 제너레이터(Generator)와 이터레이터(Iterator)

winter126 2025. 1. 31. 01:30

💡 이터레이터(Iterator)

이터레이터란, 값을 차례대로 꺼낼 수 있는 객체를 의미한다. 이는 파이썬의 리스트, 튜플, 문자열 같은 컬렉션 타입들을 의미한다. 이러한 이터러블 객체들은 iter() 함수를 사용해 이터레이터로 변환될 수 있다. 이터레이터 객체는 next() 함수를 사용해 값을 순차적으로 꺼낼 수 있다.

my_list = [1, 2, 3, 4]
my_iter = iter(my_list) # my_list에 __iter__가 호출된다.

print(next(my_iter))  # 출력: 1 # my_list에 __next__가 호출된다.
print(next(my_iter))  # 출력: 2 # my_list에 __next__가 호출된다.

 

이터레이터가 더 이상 꺼낼 요소가 없을 때 next()를 호출하면, 파이썬은 StopIteration 예외를 발생시킨다.

파이썬에서는 이터레이터를 만들기 위해 __iter__와 __next__ 메서드를 구현해야 한다.

 

 

이터레이터를 만드는 예제:

class MyIterator:
    def __init__(self, stop):
        self.currentValue = 0
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.currentValue >= self.stop:
            raise StopIteration
        result = self.currentValue
        self.currentValue += 1
        return result

my_iterator = MyIterator(5)

for i in my_iterator:
    print(i)

 

위 예제에서 MyIterator 클래스는 __iter__와 __next__ 메서드를 구현하여 이터레이터를 생성한다. 이터레이터를 사용할 때에는 for 반복문을 사용하면 된다.

 

이 값은 다시 순회할 수 없다. 다시 순회 하는 값을 만들기 위해서는 아래와 같이 코딩해야 한다.

__init__ 메서드에서 self.currentValue 변수를 0으로 초기화 하지 않고, __next__ 메서드에서 변수 초기화를 하도록 수정하면 된다.

class MyIterator:
    def __init__(self, stop):
        self.stop = stop

    def __iter__(self):
        self.currentValue = 0
        return self

    def __next__(self):
        if self.currentValue >= self.stop:
            raise StopIteration
        result = self.currentValue
        self.currentValue += 1
        return result

my_iterator = MyIterator(5)

for i in my_iterator:
    print(i)

for i in my_iterator:
    print(i)

# 결국 for는 iter먼저 실행하고, next로 StopIteration
# i = iter(li)
# next(i)

 

위 코드에서 my_iterator 객체를 두 번 사용하여 다시 순회하는 값을 만들어 준다.

 

 

아래 링크에서 시각화를 진행할 수 있다.

Python Tutor code visualizer: Visualize code in Python, JavaScript, C, C++, and Java

 

Python Tutor code visualizer: Visualize code in Python, JavaScript, C, C++, and Java

Please wait ... your code is running (up to 10 seconds) Write code in Python 3.11 [newer features not tested, select 3.6 for most stable] Python 3.6 [reliable stable version, select 3.11 for newer features] Java C (C17 + GNU extensions) C++ (C++20 + GNU ex

pythontutor.com

 

 

이러한 이터레이터는 언패킹이 가능하다.

a, b, c, d = MyIterator(4)
a

a, b, c, d = range(4)
a

 


💡 제너레이터(Generator)

제너레이터는 이터레이터를 생성하는 특별한 종류의 함수이다. 제너레이터는 yield 키워드를 사용해 값을 '생성'한다. 이터레이터와 마찬가지로 next() 함수를 통해 값들을 순차적으로 꺼낼 수 있지만, 모든 값을 메모리에 저장하는 대신 필요할 때마다 값을 생성한다. 이로 인해 메모리 사용량을 줄이고, 큰 시퀀스를 생성할 때 성능을 개선할 수 있다.

def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

counter = count_up_to(5)

print(next(counter))  # 출력: 1
print(next(counter))  # 출력: 2
# 제너레이터 컴프리헨션에서 사용했었던 문법을 함수형태의 제너레이터로 구현
def count():
    count = 2
    while True:
        yield count
        count += 2

for i, j in zip(range(10), count()):
    print(i, j)

 

제너레이터 또한 이터레이터와 마찬가지로, 더 이상 꺼낼 요소가 없을 때 next()를 호출하면 StopIteration 예외를 발생시킨다.

 

 

제너레이터를 만드는 예제:

def my_generator(data):
    for i in data:
        yield i

my_list = [1, 2, 3, 4, 5]
my_iterator = my_generator(my_list)

for i in my_iterator:
    print(i)
def infinite_generator():
    i = 0
    while True:
        yield i
        i += 1

my_iterator = infinite_generator()

for i in my_iterator:
    print(i)
    if i >= 10:
        break

 

위 예제에서 infinite_generator 함수는 while 문을 사용하여 무한반복하며, yield 키워드를 사용하여 제너레이터를 생성한다. my_iterator 객체를 for 반복문으로 순회하면서 값이 출력된다. 이터레이터를 무한히 반복시키기 때문에, break 키워드를 사용하여 10번째 이후의 값은 출력하지 않는다.

 

 

이러한 제너레이터와 이터레이터를 이용하여 zip등의 빌트인 함수를 섞으면 보다 풍부한 표현이 가능하다.

 


 

python zip은 한 번 호출된 다음 사라지는 이유

파이썬의 zip 함수는 두 개 이상의 iterable 객체를 인자로 받아서, 각 iterable 객체의 동일한 index에 위치한 요소들을 묶어서 tuple 형태로 반환한다. 이 때 zip 함수는 iterator를 반환하며, 한 번만 사용할 수 있다.

 

즉, zip 함수가 한 번 호출되면 모든 요소를 반환하고 iterator가 소멸된다.

따라서, zip 객체를 다시 사용하려면 다시 호출하여 새로운 iterator를 생성해야 한다.

 

 

예시:

a = [1, 2, 3]
b = ['a', 'b', 'c']

z = zip(a, b)
print(list(z)) # [(1, 'a'), (2, 'b'), (3, 'c')]

# zip 객체는 한 번 사용되었으므로 빈 리스트가 반환된다.
print(list(z)) # []

 

위 코드에서 zip 함수를 호출하여 zip 객체를 생성한 후, 이를 list 함수를 통해 출력하였다.

다시 list 함수를 호출하면, zip 객체는 이미 소멸되었으므로 빈 리스트가 반환된다.

 

따라서, zip 함수를 여러 번 사용해야하는 경우에는 각각 새로운 zip 객체를 생성하여 사용해야한다.

li = [1, 2, 3]
st = ['a', 'b', 'c']
z = zip(li, st)

for i in z:
    print(i)

for i in z:
    print(i)

 

일반적으로 사용되는 문자열, range, list 등의이터러블 객체는 동일하게 StopIteration이 발생하더라도 다시 호출이 가능하도록 설계가 되어 있다.

 

내부적인 설계가 궁금하다면 아래 아티클 참고!

https://junstar92.tistory.com/358

 

[Python] Iterables, Iterators, and Generators

References Fluent Python Contents Iterables and Iterators Generators and yield Generator Expressions itertools 모듈 (count, takewhile) Generator Functions in the Standard Library yield from Iterable Reducing Functions iter() Function 데이터 처리에

junstar92.tistory.com

 

li = [1, 2, 3]
st = ['a', 'b', 'c']
i = iter(zip(li, st))
next(i)
next(i)
next(i)
next(i) # StopIteration
li = [1, 2, 3]
i = iter(li)
next(i)
next(i)
next(i)
next(i) # StopIteration

 

대표적으로 zip, map, reversed, filter 가 동일하게 작동되며 sorted는 재순회 할 수 있다.

li = [1, 2, 3]
z = map(lambda x:x**2, li)

for i in z:
    print(i)

for i in z:
    print(i)
li = [1, 2, 3]
z = reversed(li)

for i in z:
    print(i)

for i in z:
    print(i)
li = [1, 2, 3]
z = filter(lambda x:x>1, li)

for i in z:
    print(i)

for i in z:
    print(i)

 

sorted는 2번의 for문 모두 순회 가능하다.

# 계속 순회가 가능하게 설계되어 있다.
li = [1, 2, 3]
z = sorted(li)

for i in z:
    print(i)

for i in z:
    print(i)

 


제너레이터 컴프리헨션 (Generator Comprehensions)

제너레이터 컴프리헨션도 비슷한 방식으로 사용될 수 있다.

gen = (x**2 for x in range(10))

print(next(gen))  # 출력: 0 (0의 제곱)
print(next(gen))  # 출력: 1 (1의 제곱)