14. 반복형, 반복자, 제너레이터

 

Remarks

이 글은 전문가를 위한 파이썬(Fluent Python)을 정리한 자료입니다.


  • reprlib.repr(): 문자열을 잘라 생성 (default: 30자)

1. Generator

  1. yield 키워드를 포함하는 함수는 모두 generator 함수이다.
     def __iter__(self):
       for word in self.words:
         yield word
       return
    
  2. 반복문, next() 등을 이용하여 값을 가져올 수 있다.
    • next() 호출 시, 함수 본체에 있는 다음 yield로 진행하며, next()는 함수 본체가 중단된 곳에서 생성된 값을 평가한다.
    • 그래서 제너레이터 함수 안에 있는 returnStopIteration 예외를 발생하게 만들 뿐이다.
  3. 제너레이터 함수는 함수 본체를 포함하는 제너레이터 객체를 생성한다.
  4. 바람직한 Sentence iterator 구현
     import re
     import reprlib
    
     RE_WORD = re.compile("\w+")
    
    
     class Sentence:
         def __init__(self, text):
             self.text = text
    
         def __repr__(self):
             return f"Sentence({reprlib.repr(self.text)})"
    
         def __iter__(self):
             return (match.group() for match in RE_WORD.finditer(self.text))
    
  5. 무한 수열을 생성하는 경우에도 유용하게 사용된다.
    • itertools.count(): 무한 등차수열 생성
    • itertools.takewhile(): 다른 제너레이터를 소비하면서 조건식(predicate)가 False가 되면 중단되는 제너레이터를 생성
        gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
      
    • itertools.compress(it, selector_it): selector_it의 해당 항목이 True일 때마다 it에서 항목을 생성
    • itertools.accumulate(it, [func]): func를 적용한 결과를 첫 번째 값으로 생성하며 it를 반복하며 누적 합계를 계산
        itertools.accumulate([3, 2, 4, 5, 1])
        itertools.accumulate([3, 2, 4, 5, 1], min)
        itertools.accumulate([3, 2, 4, 5, 1], max)
        itertools.accumulate([3, 2, 4, 5, 1], operator.mul)
      
    • itertools.chain(it1, ..., itN): it1부터 나머지 반복형의 항목을 차례대로 생성
        >>> list(itertools.chain("ABC", range(2)))
        ["A", "B", "C", 0, 1]
      
        >>> list(itertools.chain.from_iterable(enumerate("ABC")))
        [0, 'A', 1, 'B', 2, 'C']
      
    • itertools.zip_longest: zip의 longest version
    • itertools.groupby: 예제 참고
        import itertools
              
        animals = ["duck", "eagle", "rat", "giraffe", "bear", "bat", "dolphin", "shark", "lion"]
              
        # list should be sorted by the key
        animals.sort(key=len)  
        for length, group in itertools.groupby(animals, key=len):
            print(f"In group of length {length}: {list(group)}")
      
        In group of length 3: ['rat', 'bat']
        In group of length 4: ['duck', 'bear', 'lion']
        In group of length 5: ['eagle', 'shark']
        In group of length 7: ['giraffe', 'dolphin']
      
  6. iter()의 다른 사용법
    • 1이 나올 때까지 주사위 굴리기
        # Sample until 1 is selected (1 is never printed)
        for roll in iter(lambda: randint(1, 6), 1):
            print(roll)
      
    • 빈 줄을 발견하거나 파일의 끝에 도달할 때까지 한 줄씩 읽기
        with open("mydata.txt") as fp:
            for line in iter(fp.readline, ""):
                ...
      

      물론, 실제론 다음과 같은 방식이 더 좋다.

        with open("mydata.txt") as fp:
            for line in fp:
                ...
      

Appendix

  1. 객체를 반복하기 전에 객체가 반복형인지 명시적으로 검사하는 것은 필요하지 않다.
    • __iter__()를 구현하는 객체만 반복형이라 간주하지만, 그렇지 않아도 반복가능하기 때문에 오히려 헷갈릴 수 있음
      issubclass(cls, abc.Iterable), isinstance(ins, abc.Iterable)
  2. 암묵적 수치 변환
     result = type(self.begin + self.step)(self.begin)
    
  3. 오차 누적을 줄이기 위해, 여러 번의 덧셈 대신 곱셈 한 번, 덧셈 한 번만 수행
     result = self.begin + self.step * index
    
  4. math.fsum()을 이용하면 정밀도가 향상된다.