Developer/Python

python : Iterator, Generator

단님 2025. 3. 26. 21:36
728x90


Iteration

여러 항목들을 차례대로 처리하는 것이다.

data = [1, 2, 3]
for d in data :
    print(d, end=" ") # 1 2 3
Iterable

반복작업이 가능한 것을 의미한다.
iter()메서드를 가진 모든 객체가 해당된다.
ex) 리스트, 튜플, 문자열, 딕셔너리, 세트, 파일 객체..
for 루프를 통해 하나씩 처리하고 싶을때, 요소들을 반복적으로 처리해야할때, 인덱스기반의 접근이 필요할때 등 다양하게 쓰인다.

from typing import Iterable

data =[1, 2, 3]
print(isinstance(data,Iterable)) #Iterable 객체인지 확인
#True
Iterator, Generator

둘다 순회하여 순차적으로 반복처리에 쓰인다는 점은 비슷해 보이나,
메모리 사용 방식과, 구현방법이 다르다.

Iterator

다음 항목을 반환하는 역할 객체로, 현재의 위치를 기억하고 next()메서드로 다음 항목을 가져온다.
더이상 항목이 없을 경우 StopIteration 예외를 발생시키고, 한번 끝까지 순회하면 재사용할 수 없다.
구현을 하기 위해서는 클래스iter()메서드와 next()메서드가 필요하다.
iterator 같은 경우 메모리에 전체 데이터를 로드한 후 하나씩 꺼내오는 방식을 취한다.

- iterable 객체인 list 를 통한 iterator 만들기

nums = [1, 2, 3]
it = iter(nums)

print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# print(next(it))  # StopIteration 예외 발생!

- 직접 iterator 만들기

class MyIterator:
    def __init__(self, end):
        self.current = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.end:
            temp = self.current #0 을 담을 임시 변수
            self.current += 1 # 1로 변함
            return temp #0
        else:
            raise StopIteration

it = MyIterator(3) 
for i in it:
    print(i) #0, 1, 2

Generator

iterator를 생성해주는 함수로 함수 안에 yield 키워드를 사용한다.
모든 generator는 iterator이다.
느슨하게 평가되어 순서의 다음 값은
필요시에 계산된다(지연 평가)
next() 함수가 호출되면 다음 yield 문까지 실행되고, 다음 next()에서 중지된 시점부터 실행된다.
메모리에
하나씩 생성되기 때문에 메모리 효율적이라고 할 수 있다.
즉, 대용량의 데이터를 다루는데 도움이되고, 무한시퀀스를 다룰 때도 사용된다.


def my_gen():
    yield 1
    yield 2
    yield 3

gen = my_gen()

print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3

df count_gen(max):
    current = 0
    while current < max :
        temp = current
        current+=1
        yield temp
for c in cunt_gen(3):
    print(c, end=" ") # 0, 1, 2

yield from

하위의 generator 나 반복 가능한 객체(Iterable) 를 위임받아 반복하는 문법

# 기존 python
def outer():
    yield 1
    for i in [2, 3]:
        yield i
    yield 4

#yield from <Iterable>
def outer():
    yield 1
    yield from [2, 3]  # 리스트 등 반복 가능한 객체
    yield 4
    
# 둘다 결과는 동일하다. 
# 1 2 3 4

#yield from <generator>
def inner():
    yield 'A'
    yield 'B'

def outer():
    yield 1
    yield from inner()  # inner 제너레이터에 위임
    yield 2

for val in outer():
    print(val)
# 1, A, B, 2

단 set 이나 dictionary 같은 경우 순서가 보장되지 않아 생각과 다르게 흘러갈 수 있다.
iterator 본질이 값을 하나씩 꺼내는데 요점을 두기 때문에 인덱스와 함께 생각하면 안된다. 다음 값이 있느냐가 중요한 객체이기 때문이다.
dictionary 같은경우 python 3.7 버전 이상부터는 key의 순서가 유지되긴 한다. 하지만 본질은 순서가 없는 객체(unordered).

제네레이터 표현식

리스트 컴프리헨션과 비슷해 보이지만, 괄호 '( )'를 사용한다.

#리스트 컴프리헨션
squares = [x*x for x in range(5)]

#제네레이터 표현식
squares = (x*x for x in range(5))  # () 사용 => squares 는 generator 객체가 되었다.

print(next(squares))  # 0
print(next(squares))  # 1
print(next(squares))  # 4

리스트 컴프리헨션은 메모리에 리스트 전체를 생성하기 때문에 속도는 빠를 수 있으나, 메모리의 효율이 낮다.
제네레이터는 메모리에 하나씩 생성하기 때문에 속도는 느릴수있으나(대신 가볍), 메모리의 효율이 높다.

- 리스트 컴프리헨션으로 생성한 리스트와 제너레이터 표현식으로 생성한 제너레이터 size 비교

import sys

data_list = [x*x for x in range(100000)]
data_gen = (x*x for x in range(100000))

#size 비교
print("list size :", sys.getsizeof(data_list),"bytes") # 800984 bytes
print("generator size :", sys.getsizeof(data_gen),"bytes") # 200 bytes 

'Developer > Python' 카테고리의 다른 글

python : 할당과 복사  (0) 2025.03.26
python : 상속 (inheritance)  (0) 2025.03.25
python : 자료형 . 기본 자료형 및 내장 자료형  (0) 2025.03.20
python : 변수  (0) 2025.03.19