ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬의 제네레이터와 이더레이터
    Python/study 2013. 12. 18. 02:42

    ※ 주의사항

    아래 공격 코드는 연구 목적으로 작성된 것이며, 허가 받지 않은 공간에서는 테스트를 절대 금지합니다.

    악의 적인 목적으로 이용할 시 발생할 수 있는 법적 책임은 자신한테 있습니다. 이는 해당 글을 열람할 때 동의하였다는 것을 의미합니다.

    해당 문서의 저작권은 해당 저자에게 모두 있습니다. 다른 용도로 사용할 시 법적 조치가 가해질 수 있습니다.

     


    상세 분석

    이번에는 파이썬에서의 제네레이터와 이더레이터에 대하여 알아보도록 하겠습니다.


    원래 책에는 제네레이터에 대해서만 다뤘습니다. 하지만 이더레이터를 모르고 제네레이터만 알면 반쪽만 아는거라 생각되어 이더레이터를 함께 설명하도록 하겠습니다. 제네레이터는 생성기라고 부릅니다. 여기서부터 감이오지 않습니까? 생성기라는 것을 보니 뭔가 생성하는 놈인 것이라고 짐작할 수 있습니다.


    생성기가 무엇인지를 알아보기 전에 생성기와 비교되는 개념인 이더레이터에 대하여 먼저 알아보고 넘어가도록 하겠습니다.

    이더레이터(Iterator)라는 것이 무엇일까요? 예제를 통해 설명드리도록 하겠습니다.



    먼저 a라는 공간에 리스트를 생성하였습니다. 리스트 a에는 0부터 5까지의 숫자가 순차적으로 저장되어 있습니다. 그 다음 라인을 보시기 바랍니다. b라는 공간에 iter()라는 함수를 사용하여 인자로 a를 주었습니다. 그리고 b를 출력해보았더니'listiterator object at 0x7fb55621b890'이라는 메시지가 출력되었습니다. 이것은 리스트 이더레이터 객체가 표시된 메모리 주소에 위치한다는 것입니다. 여기까지가 파이썬에서 이더레이터를 생성하는 과정입니다. 

     

    기본적인 방법은 다음과 같습니다.



    이더레이터를 담을 공간을 적어주고 iter() 함수를 이용하여 인자로 리스트를 주면됩니다. 또는 생성된 리스트를 담고있는 공간을 인자로 주지 않고, 리스트 자체를 인수로 줄 수도 있습니다.


    정리 46) 공간 = iter( 리스트 ) : iter()함수를 이용하여 이더레이터를 생성합니다.



    간단하죠? 이렇게 생성된 이더레이터를 출력해보면 앞에서와 같이 해당 이더레이터가 위치한 주소만 추력됩니다. 왜 그런 것일까요? 이어서 설명하도록 하겠습니다.

     

    자, 이제 나오는 부분들이 핵심입니다. 이더레이터를 생성하고 next()라는 함수를 사용하였습니다. 그 결과가 어떻습니까? 똑같이 b.next()라고 명령을 반복해서 주고 있는데 매번 출력되는 값들이 변화합니다. 유심히 살펴보니 리스트 a에 있던 요소들과 같은 값들이 순차적으로 출력되고 있습니다. 즉, 이더레이터로 옮겨져 저장된 값들이 next()함수를 호출할 때마다 순차적으로 추출되고 있는 것입니다. 이것이 바로 이더레이션입니다. 갑자기 왠 이더레이션이냐구요? 정리해보도록 하겠습니다.


    정리 47) 이더레이터(Iterator) : 명시된 공간 'b'가 이더레이터입니다.

    정리 48) 이더레이션(Iteration) : 이더레이터 b로부터 순차적으로 요소를 가져오는 행위를 말합니다.

    정리 49) 이더레이블(Iterable) : 이더레이션이 가능하다는 의미입니다.

     

    이더레이션(iteration)이라는 단어의 뜻은 '반복'입니다. next() 함수를 사용하여 반복적으로 다음 요소를 추출한다는 행위를 반복하는 것이죠. 위에 정리된 이더레이터, 이더레이션, 이더레이블이라는 용어들이 사실 말장난 같지만 '아' 다르고 '어' 다르다고 이 부분들을 확실하게 구분할 수 있어야합니다. 


     

    자, 이제 본격적으로 이 말장난을 시작해보도록 하겠습니다. 파이썬에서 리스트라는 객체는 이더레이터일까요? 이더레이터인지 확인해보려면 next()함수를 사용해보면 됩니다.


     

    리스트를 하나 생성하고 요소를 순차적으로 추출하기위해 next()함수를 사용하였습니다. 그런데 Error를 표시하며 동작하지 않습니다. 왜냐하면 리스트는 이더레이터가 아니기 때문입니다. '그럼 아까 앞에서 리스트를 이용해서 한 것은 뭐지?'라고 생각하시는 분도 있으실 것입니다. 앞에서는 iter()함수를 사용하여 이더레이터를 생성하는데, 다만 그 이더레이터의 구성요소를 리스트로부터 가지고 온 것일 뿐입니다. 이더레이터 'b'와 리스트 'a'는 별개라는 것입니다.

    아무튼 결과적으로 리스트는 이더레이터가 아닙니다. 그럼 다음 질문입니다. 리스트는 이더레이블(Iterable)할까요?
    결과부터 말하면, 대답은 '예'입니다. 리스트가 이더레이터는 아니지만 이더레이블 합니다. 그 예를 보도록 하겠습니다.

    너무 간단해서 실망하셨습니까? 여러분은 지금 이더레이션을 보고 있습니다. 리스트가 이더레이블하기 때문에 이더레이션이 가능한 것입니다. 앞에서 리스트를 이용하여 for문을 계속 사용했는데, 이더레이션이라는 것을 몰랐기 때문에 그냥 for문이라고 보고만 있었던 것입니다. 그러나 여기에는 이더레이션, 이더레이블이라는 개념이 들어있었던 것입니다.
    사실 별거 아닌데 이름이 어려워 어렵게 느껴지는 것입니다.

    정리 50) 리스트 == 이더레이터 ? : 리스트는 이더레이터가 아닙니다. 하지만 이더레이블하고 이더레이션이 가능합니다.

    그럼 이제 이더레이터, 이더레이블, 이더레이션의 개념을 알아보았으니 제네레이터에 대하여 알아보도록 하겠습니다.

    제네레이터. 즉, 생성기는 이더레이터와 결과는 유사합니다. 하지만 그 과정에서 많은 차이가 납니다.
    먼저 생성기의 기본적인 예제를 한번 보도록 하겠습니다.


    생성기는 함수의 형태를 가집니다. num_generator(n)이라는 생성기를 생성하였습니다. 생성기는 크게 함수를 시작한다는 문자열을 출력하고 while문을 돌리고 마지막으로 함수를 종료한다는 문구를 출력합니다. 아주 간단합니다. while문은 n의 값이 6보다 작은동안 반복이 되고, while문 내에는 뭔지는 모르겠지만 yield n 이라는 명령이 있고, n의 값을 1씩 증가시킵니다.

    그리고는 for문을 이용합니다. 생성기는 일반적으로 for문과 짝을 이루어 사용됩니다. for문을 해석해보면 num_generator(0)에서 요소를 하나씩 추출해 i 라는 공간에 저장합니다. 그리고 해당 공간의 값을 출력하도록 합니다. 그런데 이상한 점이 있습니다. num_generator()라는 생성기는 반환하는 값이 없습니다. 그런데 무슨 요소를 가지고 있다고 for문을 이용하여 요소를 추출할까요?

    정리 51) 생성기와 for문 : 생성기는 일반적으로 for문과 짝을 이루어 사용합니다.

    궁금증을 해소하기 위해 for문의 결과부터 보도록 하겠습니다. 그 결과로 "Function Start" 문자열이 가장 먼저 출력되고 이후 숫자 0부터 5까지가 출력됩니다. 마지막으로 "Function End" 문자열이 출력됩니다. 출력된 결과는 '문자열-숫자-문자열'의 형태로 해당 생성기의 구조인 '문자열출력-while문-문자열출력'의 형태와 유사합니다. 따라서 for문의 결과로 출력된 숫자들은 함수 내의 while문과 관련이 있음을 알 수가 있습니다.

    while n < 6:

    yield n

    n += 1

    while문은 인자로 받은 n 값이 6보다 작은동안 반복합니다. 반복 내용으로는 yield n 이라는 명령과 n을 1증가 시키는 명령뿐입니다. yield n이라는 명령은 도대체 무엇일까요? 

    yield 는 생성기의 핵심입니다. yield를 사용하는 함수는 곧 생성기가 되는 것입니다. yield는 일반적인 함수에서의 return의 역할을 합니다. 하지만 return의 역할만을 한다면 아무런 의미가 없겠죠. 일반적인 함수에서의 return은 특정 값을 반환하는 역할을 합니다. 그리고 return을 사용하여 값을 반환하는 동시에 함수가 종료되고, 함수에서 생성된 지역변수에 해당하는 공간들은 모두 사라집니다.

    정리 52) yield : 함수 내에 yield가 포함되면 해당 함수는 생성기라고 부릅니다.

    이와달리, yield를 사용하면 값을 반환하되 함수는 종료되지 않습니다. 와...멘붕이 오시죠? 이건 도대체 무슨 말일까요? 이해를 돕기위해 그림을 그려보도록 하겠습니다.


    위의 그림은 일반적인 함수를 호출했을 때의 프로그램의 진행흐름입니다. main에서 코드를 순차적으로 수행하다가, 함수를 호출하면 진행의 흐름은 함수의 시작으로 넘어갑니다. 그리고 함수 내의 명령들을 수행하고 return을 이용하여 값을 반환하면, 진행의 흐름은 함수를 호출했던 바로 다음 부분부터 시작이 됩니다. 정리하면 함수를 한번 호출하면 함수로 갔다가 값을 반환하면 함수는 완전 빠이빠이하고 원래의 코드부분으로 돌아온다는 것입니다.

    하지만 생성기를 호출했을 때의 진행흐름은 어떨까요?


    똑같이 main에서 순차적으로 코드를 수행하다가, 생성기를 호출합니다. 그러면 프로그램 진행의 흐름은 생성기에 해당하는 함수로 넘어갑니다. 이어서 생성기에 정의된 명령들을 처리하다가 yield를 만나면 yield와 함께 명시된 값을 main으로 반환합니다. 

    하지만, 진행의 흐름만 main으로 넘어갔을 뿐 아직 생성기가 종료된 것은 아닙니다. 앞서 설명하였듯이 생성기는 for문과 짝을 이루어 사용됩니다. 그러므로 for문이 반복되면서 생성기를 다시 호출하게되고 이때, 생성기의 처음부터 실행하는 것이 아니라 이전에 값을 반환하였던 yield의 다음 부분부터 함수 내 명령을 수행하게 되는 것입니다.

    즉, yield를 이용해 값을 반환하더라도 현재의 상태를 유지하고 있는 것입니다.

    정리 53) 생성기 진행의 흐름 : 생성기는 yield를 이용해 값을 반환해도 종료되지 않고 상태를 유지합니다.

    이게 참.. 말로 설명하면 어렵습니다. 그래서 앞에서 사용한 예제를 이해하기 쉽도록 수정해보았습니다.


    앞의 예제와 똑같은 생성기와 for문입니다. 다만, 무언가 동작을 하게될 때 해당 동작이 main에서 이루어지는 동작인지, 아니면 생성기에서 이루어지는 동작인지를 파악할 수 있게, 문자열을 출력하도록 하였습니다. 문자열은 main에서 출력되는 것인지 생성기에서 출력되는 것인지 먼저 나타냈습니다. 그리고 진행의 흐름. 즉, 제어권을 받았는지 아니면 제어권을 어디로 주었는지를 나타내었습니다. 그럼 그 결과를 보도록 하겠습니다.


    위의 결과를 보면 가장 먼저 생성기가 시작되었다는 문자열을 출력합니다. 만약 생성기가 호출되고 값을 반환하면서 바로 종료되었다면 생성기가 종료되었다는 문자열이 나타날 것입니다. 하지만 위의 결과에서는 제어권이 main으로 갔다가 다시 생성기로 돌아왔다를 반복합니다. 


    그리고 생성기내에 존재하는 while문의 조건인 n < 6 을 벗어나고서야 생성기가 종료되는 것을 확인할 수 있습니다.

    이렇게 동작하는 생성기의 특징은 무엇일까요? 첫번째로 생성기는 값을 반환하여도 종료되지 않고 상태를 유지한다는 것입니다. 따라서, for문에 의해 다시 호출될 때, 유지하고 있던 상태로부터 흐름을 이어갑니다. 두번째는 이미 데이터가 저장되어 있는 상태에서 요소를 하나씩 추출해내는 이더레이터와 달리 생성기는 필요한 시점에 값을 생성하고 추출해낸다는 것입니다. 

    위와 같은 생성기의 두가지 특징이 생성기가 일반 함수나 이더레이터와 확연히 구분되는 차이점입니다.

    이렇게 열심히 설명해놓은 생성기는 파일 스트림, 데이터 흐름에 기반한 프로그램을 작성할 때 아주 유용합니다. 텍스트 파일로부터 한 라인씩 읽어와 그 안에서 특정 텍스트를 찾는 등의 동작을 하는 프로그램 등 많은 예의 프로그램에서 핵심기능으로 사용할 수 있습니다. 이런 응용에 대해서는 추후 업데이트하도록 하겠습니다.

    정리모음

    정리 46) 공간 = iter( 리스트 ) : iter()함수를 이용하여 이더레이터를 생성합니다.

    정리 47) 이더레이터(Iterator) : 명시된 공간 'b'가 이더레이터입니다.

    정리 48) 이더레이션(Iteration) : 이더레이터 b로부터 순차적으로 요소를 가져오는 행위를 말합니다.

    정리 49) 이더레이블(Iterable) : 이더레이션이 가능하다는 의미입니다.

    정리 50) 리스트 == 이더레이터 ? : 리스트는 이더레이터가 아닙니다. 하지만 이더레이블하고 이더레이션이 가능합니다.

    정리 51) 생성기와 for문 : 생성기는 일반적으로 for문과 짝을 이루어 사용합니다.

    정리 52) yield : 함수 내에 yield가 포함되면 해당 함수는 생성기라고 부릅니다.

    정리 53) 생성기 진행의 흐름 : 생성기는 yield를 이용해 값을 반환해도 종료되지 않고 상태를 유지합니다.

     

     

    참고 URL 및 도서

    - 파이썬 완벽 가이드, 데이비드 M. 비즐리 저, 2012년, 인사이트 펴냄

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

    파이썬의 모듈  (0) 2013.12.18
    파이썬의 예외처리  (0) 2013.12.18
    파이썬의 객체와 클래스  (0) 2013.12.18
    파이썬의 코루틴  (2) 2013.12.18
    파이썬의 제네레이터(추가)  (0) 2013.12.18
    파이썬의 함수  (0) 2013.12.18
    파이썬의 반복문  (0) 2013.12.18
    파이썬의 파일 입출력  (0) 2013.12.18
    파이썬의 조건문  (0) 2013.12.18
    파이썬의 자료형 - 사전  (0) 2013.12.18
Designed by Tistory.