2022년 10월 17일 월요일

파이썬 이터레이터 - 반복자 클래스


이번 주제는 파이썬 iterator입니다. 이터레이터는 번역한 것보면, 반복자라고도 하죠.
이글은 이전에 작성하다가 나두었던 글인데, 다시 정리해서 올려봅니다

파이썬에서 나오는 ~레이터, 이터레이터, 제네레이터, 데코레이터,, 이중에서 데코레이터는 앞에 두개랑 크게 상관이 없고요,

이터레이터 와 제네레이터가 관련이 좀 있습니다.

iterator를 이야기 하기전에 일단 iterable이라는 말을 집고 갑시다, iterable 이라는 의미는 반복이 가능하다는 의미입니다.

파이썬의 기본 데이터형인 리스트, 튜플, 딕셔너리등이 iterable이라고 합니다.

그럼 이터레이터는?

이것을 실제로 동작을 시키는 부분을 이터레이터라고 합니다, 리스트등을 이터레이터에 넣어서 데이터를 하나씩 빼내어 주는 용도 입니다,

이터레이터는 기본적으로 design pattern에서 많이 사용되는 용어입니다

우리가 어떤 반복문을 돌릴때, 이전에는 주로 for 문을 가지고 반복문을 만드는데, 이것을 좀 더 추상화, 일반화 시킨 개념이 이터레이터입니다, 예전에 C에서 반복문을 짤때는 다음과 같은식으로 시작을 했습니다

 for (i = 0; i < 10 ; i++ )  ; 
     // action by array[i] 

이런식으로 명시적으로 반복문의 구조를 써줬는데,

사이즈로 신경쓸필요 없이 item들을 가져다 달라는 구문입니다. 다른 언어중 foreach 로 시작하는 구문들이 이런 유사한 방식입니다 .

부수적인 정보들 예를 들면, 사이즈등은 신경쓰고 싶지 않고, Array를 줄테니 알아서, 하나씩 빼줘 하는 개념이 이터레이터입니다.

여기서 Array가 List, dictionary, tuple이 되는 것겠죠,

이것이 꼭 정해진 data type이 아니라, 꼭 이 새로운 클래스에서도 iter함수를 구현하면, iterator를 사용할수 가 있습니다.

꼭 배열이 아니라 집합이라든지, 어떤 object건 간에 iterator 를 호출하면, 미리 정의된대로 하나씩 불러오게 할 수 있습니다.

어떤 큰단위가 있고, 하나씩 세부 아이템을 호출하는 용도입니다. 일반적으로 list, dictionary같은 형태는 for문에 넣으면 잘 동작합니다.

아래와 같이 리스트, 딕셔너리, 스트링 전부 잘 이터레이션이 됩니다.

for x in [1,2,3]: # 리스트
    print(x)
for x in (1,2,3): # 튜플
    print(x)
for x in 'abc': # 문자열
    print(x)

여기까지는 큰 문제 없이 몇번 사용하보면 이해가 쉬울것입니다.

그러면, class에도 for문에 적용을 해보고 싶을때가 있습니다. 클래스를 이터러블하게 만드는 방법입니다. iter() 함수를 class 내부에 구현하는 방법입니다.

next 와 StopIteration을 구현을 해줍니다. 이건 제네레이터에서도 나온 문법인데,

먼저 next() 는 다음값을 구해주는 연산입니다. 그리고 StopIteration은 더이상 값이 없을때 호출해 줍니다.

이런 식으로 iteration을 구현할 수 있습니다

예제를 하나 알아봅니다. 무한 수열을 만들어봅니다. 리스트가 작으면 그냥 기입을 하면 되겠는데, 이 끝을 알수 없는 형태가 나오면, 무작정 나열할 수 가 없습니다.

x = [1,2,3,4,......,]  # fail

먼저아래와 같이 무한수열을 class로 선언해 줍니다.

class Infite:
    def __init__(self) -> None:
        pass

a = Infite()

for x in a:
    print (x)

'''
Traceback (most recent call last):
  File "infinite.py", line 10, in <module>
    for x in a:
TypeError: 'Infite' object is not iterable
'''

위 코드를 실행시키면 object가 iterable하지 않다고 에러가 납니다.

이 클래스를 이터러블하게 만들어 봅니다. __iter__와 __next__를 추가해줍니다. __iter__는 iterable할때 초기화 시키는 함수라고 보면 되고, 아래는 직접 자기자신을 리턴하긴 했지만, 내부 리스트나 딕셔너리등을 리턴할 수도 있습니다. 여기서는 0으로 초기화 해줍니다. __next__에서는 리턴값을 더해줍니다. 내부 $self.cnt$에 +1하는 값을 더합니다. 이와 같이 구현할 경우, 종료값이 없으므로 무한수열 입니다. 5에서 break를 걸었는데, break가 없으면 이 루프는 무한으로 동작합니다.

class Infite:
    def __init__(self) -> None:
        self.cnt = 1
       
    def __iter__(self):
        self.cnt =0
        return self
    def __next__(self):
        self.cnt +=1
        return self.cnt

a = Infite()

for i,x in enumerate(a):
    print (x,end=' ')
    if i == 5:
        break
print ('')

# 1 2 3 4 5 6 

여기서 next에 StopIteration을 추가해 봅니다. 아래와 같이 internal counter가 9에 도달할 경우, raise StopIteration을 호출해서, loop를 종료합니다. 실제로 무한루프가 돌면, 끝나지 않으니, 이런 식으로 종료를 시켜줄수 있겠죠. 아래 예제에서는 break구문없이 class내부에서 StopIteration을 호출해서, 마무리를 지어줍니다.

class Infite:
    def __init__(self) -> None:
        self.cnt = 1
       
    def __iter__(self):
        self.cnt =0
        return self
    def __next__(self):
        self.cnt +=1
        if self.cnt == 9:
            raise StopIteration
        return self.cnt

a = Infite()

for i,x in enumerate(a):
    print (x,end=' ')
print ('')

# 1 2 3 4 5 6 7 8

이상으로 이터레이션과 이터러블한 객체를 만드는 법을 정리해보았습니다. 위 예는 단순한 예이지만, 실제로 객체를 이터러블하게 만드는 방법은 실제로도 많이 쓰입니다. 이 내용을 참고하셔서 활용하시면 좋을것 같습니다.



댓글 없음:

댓글 쓰기