본문 바로가기
Sprint_DA01/보충학습

241025 보충학습 #3 Iterator and Generator

by Toddler_AD 2024. 11. 29.

1. Iterator(이터레이터)

  • 순차적으로 데이터에 접근할 수 있는 객체
  • 순서대로 다음 값을 리턴할 수 있는 객체

1) Iterable 하다.

  • list, dict, tuple 객체는 타입이 다른 여러가지 원소를 가질 수 있습니다.
  • 이 객체들은 일반적으로 for loop를 통해 순서대로 원소들을 가져오게 됩니다.
  • 이 때, 이 객체를 iterable한 객체라고 합니다.
my_list = [1,2,3,4]

for i in my_list:
    print(i)
    
## 이터레이터는 아니기 때문에 next() 메소드 사용시 에러 발생
next(my_list)

 

 

 

 

2) Iterator와 차이?

  • Iterator -> Iterable 하다!
  • Iterator가 아닌 list, tuple 등의 객체 -> Iterable 하다!
  • Iterator는 iter() 메소드를 활용하여 만들 수 있으며, 내부에 next() 객체가 존재한다.
  • 따라서, Iterable 한 것 중 내부에 next() 객체가 존재하는 것만이 Iterator가 될 수 있다.
  • Iterator 객체는 항상 iterable입니다.
my_list = [1,2,3,4]

## 이터레이터로 변경 (list -> iterator)
my_iter = iter(my_list)

## next() 메소드 사용하여 원소에 하나씩 접근 가능
next(my_iter)

 

 

 

 

3) Iterator를 반환하는 함수들

  • map
my_list = [1, 2, 3, 4]

# map 이터레이터
map_iter = map(lambda x: x * 2, my_list)
# print(list(map_iter))  # [2, 4, 6, 8]

next(map_iter)
  • filter
my_list = [1, 2, 3, 4]

# filter 이터레이터
filter_iter = filter(lambda x: x % 2 == 0, my_list)
# print(list(filter_iter))  # [2, 4]

next(filter_iter)
  • zip
my_list = [1, 2, 3, 4]

# zip 이터레이터
zip_iter = zip([1, 2], ['a', 'b'])
# print(list(zip_iter))  # [(1, 'a'), (2, 'b')]

next(zip_iter)

 

 

이 외에도

  • itertools.count(start, step): 무한히 증가하는 이터레이터를 생성.
  • itertools.cycle(iterable): iterable을 반복하는 이터레이터를 생성.
  • itertools.repeat(elem, n): 동일한 값을 n번 반복하는 이터레이터를 생성.

 

 

2. Generator

  • Iterator의 일종 혹은 특수한 형태로, yield키워드를 사용하여 값을 동적으로 반환한다.
  • 동적으로 반환한다. -> 값을 미리 모두 계산해두지 않고, 필요할 때 그때그때 생성해서 반환.
  • 그렇기 때문에 Iterable 객체의 크기가 아주 커도 이를 모두 메모리에 올려두고 사용하지 않기 때문에 메모리 효율이 좋아진다.

 

1) Iterator와 Generator의 공통점  

  • next() 메서드를 통해 하나씩 값을 반환하며, 값이 없을 때 StopIteration 예외를 발생

2) generator 생성 방법

  • 아래와 같이 4가지 방법을 통해 generator 객체를 생성할 수 있다.
## 1
def my_generator():
    yield 1
    yield 2
    yield 3
    yield 4
    yield 5
    
gen1 = my_generator()

## 2    
gen2 = (i for i in range(1,6))

## 3
def my_generator():
    for i in range(1,6):
        yield i
        
gen3 = my_generator()

## 4
def my_generator():
    yield from [i for i in range(1,6)]
    
gen4 = my_generator()

 

 

 

3) Generator를 사용하는 목적

  • 메모리 사용량 절약
    • Iterator 의 경우 생성시 반복의 대상이 되는 객체 + Iterator 객체 이만큼의 메모리가 소비된다.
    • 반면, Generator의 경우 반복의 대상이 되는 객체에 대한 메모리 소비는 없고, Generator 객체에 대한 메모리만 소비된다.
import sys

# 리스트를 사용한 메모리 측정
def create_list(n):
    return [i for i in range(n)]

# 제너레이터를 사용한 메모리 측정
def create_generator(n):
    for i in range(n):
        yield i

# 리스트와 제너레이터 메모리 사용 비교
n = 1000000
list_data = create_list(n)
iter_data = iter(list_data)
gen_data = create_generator(n)

print(f"Iteraor memory usage : {sys.getsizeof(list_data) + sys.getsizeof(iter_data)} bytes")
print(f"Generator memory usage : {sys.getsizeof(gen_data)} bytes")

---
Iteraor memory usage : 8448776 bytes
Generator memory usage : 112 bytes

 

 

  • 초대용량 데이터를 대상으로 반복문 실행이 필요할 때
    • 굉장히 큰 데이터를 대상으로 일정한 반복 작업이 필요할 때, 일반적인 for문을 사용하게 되면 일단 데이터를 메모리에 올리는 작업이 선행된다. 이 경우 서버의 리소스가 부족할 경우 작업이 취소되고, 리소스 여유가 있더라도 데이터를 읽어들이는데 시간이 많이 소요될 수 있다.
    • 이 때, 데이터를 대상으로 Generator를 생성하여 내부 원소들에 대한 반복 작업 처리를 한다면 리소스 및 시간을 많이 절약할 수 있다.
  • 무한한 자연수를 대상으로 반복문을 실행
# 무한한 자연수를 생성하는 제너레이터
def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

# 제너레이터 사용
gen = infinite_sequence()
for i in range(5):  # 무한 루프에서 5개만 출력
    print(next(gen))

 

 

  • 대용량 csv 데이터에 대한 pandas dataframe 형상을 보고자 할 때
    • 특정 csv 파일을 pandas로 읽어와 dataframe 형상을 보고싶을 때, csv 파일이 너무 방대하면 read_csv 하는데 많은 시간이 걸리거나, 읽어오지 못하는 경우가 생길 수 있다.
    • Generator를 활용하면 데이터의 특정 행까지만 불러와 형상 파악이 가능하다.
import pandas as pd

## 데이터의 10행 까지만 가져오는 Generator 생성
def get_df():
    df = pd.read_csv('dataset.csv')
    for i in range(10):
        yield df.iloc[i]
        
gen = get_df()

## generator에서 데이터를 불러와 DataFrame 생성
pd.DataFrame(list(gen))

'Sprint_DA01 > 보충학습' 카테고리의 다른 글

241018 보충학습 #2 Decorator  (0) 2024.11.29
241115 보충학습 #4 F-String  (2) 2024.11.23
241122 보충학습 #5 Error and Exception  (0) 2024.11.22