iterator와 generator

iterator

  • next() 함수에 의해서 값을 하나씩 반환(next 호출 시점)

  • StopIteration

  • custom iterator

    • 정의한 클래스가 이터레이터를 지원하려면 iter 메서드를 정의해야함

    • iter 메서드에는 자신을 돌려주면 됨

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      # custom iterator
      class MyIter():

      # iterator를 구성하려면 __iter__, __next__ 메소드가 필요하다.

      def __init__(self, li):
      self.container = li
      self.index = 0

      def __iter__(self):
      # 이터레이터를 지원을 위한 __iter__ 메소드를 정의
      # 자신을 돌려줌
      return self

      def __next__(self):
      # 어느 시점에 StopIteration 에러만 넘겨줄 수 있으면
      if self.index >= len(self.container):
      raise StopIteration

      ret = self.container[self.index]
      self.index += 1
      return ret

      li = [1, 2, 3, 4, 5]

      it = iter(li)

      type(it)
      >> list_iterator

      next(it)
      >> 1
      next(it)
      >> 2
      next(it)
      >> 3
      next(it)
      >> 4
      next(it)
      >> 5

      # StopIteration 에러 발생
      next(it)
      >> StopIteration


      # for문에서 li는 자동으로 iterator 객체가 된다.
      for e in li:
      print(e, end = ' ')
      >> 1 2 3 4 5

      it_obj = MyIter(li)

      it = iter(it_obj)

      type(it)
      >> __main__.MyIter

      next(it)
      >> 1
      next(it)
      >> 2
      next(it)
      >> 3
      next(it)
      >> 4
      next(it)
      >> 5

      # next() 호출 시점에서 지정한 예외가 발생할 수 있도록 조건 지정
      # StopIteration 에러 발생
      next(it)
      >> StopIteration
    • byte stream

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      f = open('iterator_test.txt', 'rt') # 텍스트 파일을 불러옴
      # 텍스트 파일 내용
      """
      Single responsibility principle
      Open-closed principle
      Liskov substitution principle
      Interface segregation princile
      Dependency inversion principle


      END
      """
      f.readline() # 텍스트 파일 한줄을 읽어옴
      >> 'Single responsibility principle\n'

      class Reader:

      def __init__(self, filename):

      # open()의 두 번째 인자는 mode
      # 'r' = 읽기 전용
      # 't' = 텍스트 모드(기본값)
      self.f = open(filename, 'rt')

      def __iter__(self):

      return self

      def __next__(self):

      line = self.f.readline() # readline() - 한번에 한 줄씩 읽기

      if line:
      return line[:-1] # 문자열 끝에 \n를 빼고 반환
      else:
      self.f.close()
      raise StopIteration

      reader = Reader('iterator_test.txt')

      it = iter(reader)


      for _ in range(15):
      print(next(it))

      # 총 텍스트의 줄 수를 벗어나 StopIteration 발생
      >> Single responsibility principle
      Open-closed principle
      Liskov substitution principle
      Interface segregation principle
      Dependency inversion principle


      EN
      ---------------------------------
      StopIteration

generator

  • 독특한(?) 함수

  • body 안에 yield

    • 파이썬 코루틴의 구현은 generator를 기반으로 한다.

    • 파이썬 stack frame이 heap에 저장되어 있기 때문에 가능하다.

    • return 값은 StopIteration이 발생했을 때 반환된다.

    • StopIteration도 객체이다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      def gen():
      print('gen start')
      yield 1
      print('abcde')
      yield 2
      print('fghjk')
      yield 3
      print('asdfasdf')
      yield 4
      print('123123123')
      return 'done'
    • 모든 제네레이터는 이터레이터이다.

    • 제네레이터는 게으른 팩토리이다.(값을 그 때 그 때 생성함)

    • 제네레이터를 호출했을 때 제네레이터 객체가 생성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      g = gen()
      g
      >> <generator object gen at 0x001727F0>

      # 첫 번째 next() 호출하면 yield를 만날 때까지 함수가 실행이 된다.
      next(g)
      >> gen start
      1

      next(g)
      >> abcde
      2

      a = next(g)
      >> fghjk
      a
      >> 3

      next(g)
      >> asdfasdf
      4

      # 함수가 리턴을 만나면 StopIteration 객체가 생성
      # value에 지정해놓은 리턴 값을 반환한다.
      try:
      next(g)
      except StopIteration as exc:
      print(exc.value)
      pass

      >> 123123123
      done
    • coroutine function

    • 함수 실행 도중에 실행 주도권을 다른 함수에 넘길 수 있음

    • 내가 원하는 시점(non - preemptive)에 다시 실행 주도권을 가져올 수 있는 함수

    • 명시적(explicit)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # 제네레이터로 피보나치 만들기
      def fibo_gen(n):

      a = 0
      b = 1

      for _ in range(n):
      yield a
      a,b = b, a+b

      fibo = fibo_gen(15)

      for _ in range(15):
      print(next(fibo), end = " ")
      >> 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
    • yield from

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      def gen():
      print('gen start')
      data1 = yield 1
      print(f'gen[1] : {data1}')
      data2 = yield 2
      print(f'gen[2] : {data2}')
      return 'END'

      # yield가 들어가있으면 generator
      # yield from 다음에는 generator 객체가 들어와야 함
      # yield from은 중계 역할
      # yield from 다음으로 나오는 generator 객체가 끝날 때까지 중계 역할
      # g = gen() 부분은 generator 객체이기 때문에 함수가 실행되지 않는다.
      # 만약 generator 객체가 아니고 일반 함수였다면 실행이 되야 한다.

      # delegate - 위임
      def delegate():
      g = gen()
      print('start')
      ret = yield from g
      print('end')
      print(f'return value : {ret}')
      return ret

      g = delegate()
      g
      >> <generator object delegate at 0x006C1430>

      first = g.send(None)
      >> start
      gen start

      second = g.send('world')
      >> gen[1] : world

      second
      >> 2

      # 'END'라는 generator g 객체의 리턴 값을 StopIteration 객체가 value에 넣음
      try:
      g.send('hello')
      except StopIteration as exc:
      print(exc.value)
      >> gen[2] : hello
      end
      return value : END
      END
Share