ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] Tip - 이터레이터를 병렬로 처리하려면 zip을 사용
    언어/파이썬 & 장고 2016. 10. 9. 19:31

    파이썬에서 관련 객체로 구성된 리스트를 많이 사용합니다. list comprehension을 사용하면 소스 리스트 (source list)에 표현식을 적용하여 파생 리스트(derived list)를 쉽게 얻을 수 있습니다.

    파생 리스트의 아이템과 소스 리스트의 아이템은 서로의 인덱스로 연관되어 있어 두 리스트를 병렬로 순회하려면 소스 리스트 길이만큼 순회하면 됩니다.

    names = ['cecilia', 'lise', 'marie']
    letters = [len(n) for n in names]
    longest_name = None
    max_letters = 0
    for i in range(len(names)):
        count = letters[i]
        if count > max_letters:
            longest_name = names[i]
            max_letters = count
    print(longest_name)
    # 결과
    # cecilia
    


    위의 문제는 전체 루프문이 보기가 안좋다는 것입니다. names와 letters를 인덱스로 접근하면 코드를 읽기 어려워집니다. 루프의 인덱스 i로 배열에 접근하는 동작이 두번 일어나기 때문입니다. enumerate를 사용하면 해당 문제점을 개선할 수 있지만 여전히 완벽하지는 않습니다.

    for i in enumerate(names):
        count = letters[i]
        if count > max_letters:
            longest_name = names[i]
            max_letters = count
    print(longest_name)


    파이썬 3에서 zip은 지연 제너레이터로 이터레이터 두 개 이상을 감쌉니다. zip 제너레이터는 각 이터레이터로부터 다음 값을 담은 튜플을 얻어옵니다. zip 제너레이터를 사용한 코드는 다중 리스트에서 인덱스로 접근하는 코드보다 훨씬 명료합니다.

    for name, count in zip(names, letters):
        if count > max_letters:
            longest_name = name
            max_letters = count
    print(longest_name)


    내장함수 zip을 사용할 때는 두 가지 문제가 있습니다.


    첫 번째 문제는 파이썬 2.x에서 제공하는 zip이 제너레이터가 아니라는 점입니다. 제공한 이터레이터를 완전히 순회해 zip으로 생성한 모든 튜플을 반환합니다. 이 과정에서 메모리를 많이 사용하여 프로그램이 다운되는 원인이 되기도 하기 때문에 파이썬 2.x에서 큰 이터레이터를 zip으로 묶어서 사용하고자 한다면 내장 모듈인 itertools에 있는 izip을 사용해야 합니다.


    두 번째 문제는 입력 이터레이터들의 길이가 다르면 zip이 이상하게 동작한다는 점입니다. 예를 들어 names 리스트에 다른 이름을 추가했지만 letters의 카운터를 업데이트하는 것을 잊었다고 가정하면 두 입력 리스트에 zip을 실행했을 때, 예상치 못한 결과가 나옵니다.

    names = ['cecilia', 'lise', 'marie']
    letters = [len(n) for n in names]
    longest_name = None
    max_letters = 0
    
    names.append('rosalind')
    for name, count in zip(names, letters):
        print(name)
    # 결과
    # cecilia
    # lise
    # marie


    새로 넣은 'rosalind'가 결과에 없습니다. zip은 감싼 이터레이터가 끝날 때까지 튜플을 계속 넘겨줍니다. 이 방식은 이터레이터들의 길이가 같을 때는 제대로 동작합니다. list comprehension에서 생성된 파생 리스트의 경우가 이에 해당합니다. 그 외에 많은 경우 zip의 잘라내기 동작은 이상합니다. zip으로 실행할 리스트의 길이가 같다고 확신할 수 없다면 내장모듈 itertools의 zip_longest를 사용하는 방안을 고려해야 합니다. (파이썬2.x에서는 izip_longest)

    from itertools import zip_longest
    
    names = ['cecilia', 'lise', 'marie']
    letters = [len(n) for n in names]
    longest_name = None
    max_letters = 0
    
    names.append('rosalind')
    for name, count in zip_longest(names, letters):
        print(name)
    
    # 결과
    # cecilia
    # lise
    # marie
    # rosalind
    

    요약

    내장함수 zip은 여러 이터레이터를 병렬로 순회할 수 있음

    파이썬 3.x의 zip은 튜플을 생성하는 지연 제너레이터. 파이썬 2.x의 zip은 전체 결과를 튜플 리스트로 반환

    길이가 다른 이터레이터를 사용하면 zip은 결과를 조용히 잘라냄

    내장 모듈 itertools의 zip_longest 함수를 쓰면 여러 이터레이터를 길이에 상관없이 병렬로 순회 가능


    댓글