본문 바로가기
컴퓨터공학 언어/Python

iterator, generator

by KChang 2021. 10. 27.

반복자 iterator

Iterable 객체 - 반복 가능한 객체

대표적으로 iterable한 타입 : list, dict, set, str, bytes, tuple, range

iterable한 타입을 확인하는 방법

collections.iterable에 속한 instance인지 확인 : isinstance 함수는 첫 번째 파라미터, 두 번째 파라미터 클래스의 instance이면 True 반환한다.

import collections.abc

var_list = [1,3,4,5]
print(isinstance(var_list, collections.Iterable))

 

iterator 객체 : 하나 이상의 항목이 포함되어 있는 자료구조에서 데이터를 차례대로 꺼낼 수 있는 객체

iterator는 iterable한 객체를 내장함수 또는 iterable 객체의 메소드로 객체를 생성할 수 있다.

(반복 불가능 자료형, 자료형 변환 불가능)

 

iterator 객체는 next 내장 함수를 사용할 때마다 첫 번째, 두 번째, 세 번째 값이 출력된다.

a = [1,2,3]
a_iter = iter(a)

for _ in range(0,3):
    print(next(a_iter), end = ' ')

# 결과 
1 2 3

 

iterator 매직 메소드 __next__를 통해 하나씩 값을 꺼냄 (마지막 원소 반환 후에는 StopIteration을 반환한다.)

b = {1,2,3}
b_iter = b.__iter__()
# b_iter.__next__()
for _ in range(0,3):
    print(b_iter.__next__(), end = ' ')
# 결과 
1 2 3

 

리스트, 튜플은 iterator로 변환 가능, 정수 값은 불가능

 

Iterable 객체와 Iterator 객체

파이썬의 어떤 객체가 __iter__메소드를 포함하고 있다면 해당 객체를 Iterable 객체라고 부른다.

iter라는 내장 함수를 호출하면 내부적으로 해당 객체의 __iter__메소드를 호출하게 된다.

__iter__ 메소드는 Iterator 객체를 반환해주는데, Iterator 객체는 __next__메소드를 반드시 구현하고 있어야 한다.

 

1) Iterable

  • __iter__ 메소드 사용시 : 내부적으로 iterator 객체를 리턴한다.
  • __iter__ 추상메소드를 실제로 구현해야 하며 이 메소드는 호출될 때마다 새로운 Iterator를 반환해야 한다.

2) Iterator

  • __next__ 메소드 사용시 : next 내장 함수에 대해서 동작한다. 따라서 iterator 객체이다.
  • __iter__를 구현하되 자기 자신을 반환해야 한다.

 

Iterator 객체 만들기

1) __iter__메소드 구현 : __iter__ 메소드는 Iterator 객체를 리턴해야 하는데 Season 클래스 자기 자신이 Iterator 객체(__next__를 포함한다.)이므로 자기 자신(self)를 리턴하도록 구현한다.

2) __next__메소드 구현 : for 문에서 각 반복마다 계절의 이름이 순서대로 출력되는 기능을 __next__메소드에 구현한다.

class Season : 
    def __init__(self):
        self.data = ["봄","여름","가을","겨울"]
        self.index = 0
    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            cur_season = self.data[self.index]
            self.index += 1
            return cur_season
        else:
            raise StopIteration

3) Season 클래스의 객체를 생성한 후 iter 내장함수를 호출하여 Iterator 객체를 생성한다.

다음, Iterator 객체에 대해서 다시 next 내장 함수를 호출하여 반복적으로 값을 얻는다.

s = Season()
ir = iter(s)
print(next(ir))
print(next(ir))
print(next(ir))
print(next(ir))
  • 보통 iterator 객체를 얻기 위해 iter 내장 함수에 iterable 객체를 입력한다. 그런데 iter 내장함수의 리턴 값인 iterator 객체를 다시 iter에 넣어도 전달된 iterator 객체를 그대로 리턴해준다.
iterator객체

OddCounter 클래스의 정의와 객체 생성

class OddCounter:
    # 1부터 증가하는 홀수를 반환하는 클래스
    def __init__(self, n = 1):  # 초기화 메소드, n를 1로 둔다.
        self.n = n

    def __iter__(self):         # 반복자 __iter__() 함수를 가져야 한다.
        return self

    def __next__(self):         # 반복자는 __next__() 함수를 가져야 한다.
        t = self.n              # self.n을 임시 변수 t에 지정해 두고
        self.n += 2             # self.n을 2씩 증가
        return t                # t부터 출력해야 1이 가장 먼저 출력된다.



my_counter = OddCounter()
for x in my_counter:
    if x > 20:                  # 반복을 종료하는 조건 : x > 20
        break
    print(x, end= ' ')


# 결과
1 3 5 7 9 11 13 15 17 19

 

OddCounter 클래스와 StopIteration 예외 생성 가능

class OddCounter:
    # 1부터 증가하는 홀수를 반환하는 클래스
    def __init__(self, n = 1):  # 초기화 메소드, n를 1로 둔다.
        self.n = n

    def __iter__(self):         # 반복자 __iter__() 함수를 가져야 한다.
        return self

    def __next__(self):         # 반복자는 __next__() 함수를 가져야 한다.
        if self.n < 20:
            t = self.n              # self.n을 임시 변수 t에 지정해 두고
            self.n += 2             # self.n을 2씩 증가
            return t                # t부터 출력해야 1이 가장 먼저 출력된다.

        raise StopIteration     # 조건을 만족하지 않으면 StopIteration을 raise한다.



my_counter = OddCounter()
for x in my_counter:
    print(x, end= ' ')

# 결과
1 3 5 7 9 11 13 15 17 19

 

반복가능 객체를 위한 내장함수

파이썬의 반복기능 iterable 객체는 다양한 내장함수들을 적용할 수 있다.

  • all() : 반복 가능한 항목들이 모두 참일 때 참을 반환한다.
l1 = [1,2,3,4]
l2 = [0,2,4,8]
l3 = [0,0,0,0]

print(all(l1))  # 모든 요소가 true(0이 아닌 값)일 때만 True를 반환한다.
# True
  • any() : 임의의 반복 가능한 항목들 중에서 참이 하나라도 있을 경우 참을 반환한다.
print(any(l3))  # 항목들 중에서 하나라도 true(0이 아닌 값)일 경우 True를 반환한다.
# False
  • bool() : 값을(리스트) 부울 값으로 변환한다. 즉 리스트의 항목 유무를 True와 False를 알려준다.

 

Generator

  • generator 함수 : iterator를 생성해주는 함수, 함수 안에 return 키워드 대신 yield 키워드 사용한다.
  • yield 문
    • 값을 반환한 후 계속 진행
    • 실행 상태를 그대로 저장해 둔 채로 잠시 멈춘다.
    • 나중에 다시 이전에 멈춘 상태에서부터 이어서 다음 코드를 실행할 수 있게 된다.
  • iterable한 순서가 지정된다.
  • 함수의 내부 로컬 변수를 통해 내부상태가 유지된다.
  • 무한한 순서가 있는 객체를 모델링할 수 있다.

ex) 잘못된 일처리 방식 : 빵이 100개 될 때까지 순서대로 하나씩 빵을 구은 후 100개가 준비되면 이를 점원에게 한 번에 전달

  • 파이썬의 함수는 호출하면 호출이 끝날 때까지 호출부가 대기하고 있다가 함수가 리턴한 값을 받아서 사용하게 된다.
빵

빵을 낱개로 포장하는 거라면 제빵사가 빵을 한 번에 100개를 전달하는 것이 아니라 2개나 5개 처럼 빵이 일부라도 준비가 될 때 전달해주는 것이다.

  • 파이썬 함수도 그때 그때 준비된 값을 리턴하고 다시 돌아와서 함수 내 코드를 실행하고 다시 준비되면 리턴할 수 있다.

 

파이썬은 반복자 말고도 generator라는 객체를 제공하는데 이 객체는 모든 값을 메모리에 올려두고 이용하는 것이 아니라 필요할 때마다 생성해서 반환하는 일을 한다.

(메모리를 효율적으로 활용할 수 있다는 장점이 있다.)

my_generator = (x for x in range(1,4))
for n in  my_generator:
    print(n)

print(type(my_generator))

for 문에서 필요로 할 때마다 반환해주고 메모리에서 보관하지 않는다.

 

generator와 yield

yield 문 : 함수를 종결하지 않으면서 값을 계속 반환

generator 객체를 생성했다면 next()함수 호출을 통해 generator 객체 내의 코드를 실행할 수 있는 이때 yield 구문을 만나면 파이썬 함수의 return처럼 yield 키워드에 있는 값(그림에서 i)을 호출부로 리턴하고 실행의 흐름도 호출부로 이동한다.

yiedl

generator 함수를 호출하면, generator 클래스의 객체가 생성된다.

def genFunc():
    for i in range(3):
        yield i


gen = genFunc()
print(gen,type(gen))

# 결과
<generator object genFunc at 0x000001E49B10FF90> <class 'generator'>
  • gen 변수가 바인딩하는 generator 객체는 next(g)를 호출할 때 0, 1, 2라는 값을 순서대로 돌려준다. 이때 한 번더 next(g)를 호출하면 더이상 돌려줄 값이 없기 때문에 StopIteration이라는 에러가 발생한다.
  • next(g)를 사용하지않고 generator로부터 값을 가져오는 쉬운 방법
def genFunc():
    for i in range(3):
        yield i

gen = genFunc()
for i in gen:
    print(i)

# 결과
0 1 2

for문을 사용하면 반복할 때마다 내부적으로 next(g)를 호출하는데 이는 StopIteration이 발생할 때까지 계속된다.

 

List, Set, Dictionary의 표현식의 내부도 generator이다.

 

yield from

yield문을 여러 번 바깥으로 전달하려면 for문을 사용해야 한다.

def three_generator():
    a = [1, 2, 3]
    for i in a:
        yield i

gen = three_generator()
list(gen)

# 결과
[1, 2, 3]

for문 대신에 iterable한 객체를 yield할 때는 yield from iterable로 값을 전달할 수 있다.

def three_generator2():
    a = [1, 2, 3]
    yield  from a

gen = three_generator2()
print(list(gen))

# 결과
[1, 2, 3]

 

 

iterator와 generator 정리

iterator : 요소가 복수인 컨테이너(리스트, 튜플, 셋, 사전, 문자열)에서 각 요소를 하나씩 꺼내 어떤 처리를 수행할 수 있도록 하는 간편한 방법을 제공하는 객체

generator : iterator의 한 종류로, 하나의 요소를 꺼내려고 할 때마다 요소 generator를 수행하는 타입으로, Python에서는 yield문을 통해 구현하는 것이 보통이다.

 

python의 내장 컬렉션 (list, tuple, set, dic 등)의 어떤 것이든 iterable을 상속받고, 내장의 컬렉션을 사용한 반복문 처리로 미리 컬렉션 값을 입력해 둬야 할 필요가 있으므로 아래와 같은 경우는 interator 또는 generator을 직접 구현하고 싶다고 생각하는 경우가 있을 것이다.

  • 무한히 반복하는 iterator
  • 모든 요소를 미리 계산하는 것이 혹은 가져 오는 것이 계산 비용, 처리 시간, 메모리 사용량 등의 측면에서 힘든 경우

iterator와 generator의 관계도

iterator, generator 관계도

 

(cf) 각 자료구조의 iterable, Generator, Iterator의 상속 여부

  • list는 Iterable를 상속 ? True, set는 Iterable를 상속 ? True
  • list는 Iterator를 상속 ? False, set는 Iterator를 상속 ? False
  • list는 Generator를 상속 ? False, set는 Generator를 상속 ? False
  • tuple는 Iterable를 상속 ? True, dict는 Iterable를 상속 ? True
  • tuple는 Iterator를 상속 ? False, dict는 Iterable를 상속 ? False
  • tuple는 Generator를 상속 ? False, dict는 Generator를 상속 ? False

 

언더 스코어('_')

1) __이름__ : 내장된 특수한 함수와 변수를 나타낸다.

2) 변수명_ : 예약어를 변수명으로 사용할 수 없을 때 사용한다.

3) _함수 : 한 모듈 내부에서만 사용한 private 클래스/함수/변수/메서드를 선언할 때 사용하는 컨벤션

 

 


참고 자료

'컴퓨터공학 언어 > Python' 카테고리의 다른 글

파일 입출력  (0) 2021.10.28
내부 함수, reduce 함수  (0) 2021.10.27
Algorithm 공부하다 알게된 내용  (0) 2021.10.26
Module과 Package  (0) 2021.10.15
함수  (0) 2021.10.15

댓글