Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 201 – 록스텝 반복

Author:
Barry Warsaw <barry at python.org>
Status:
Final
Type:
Standards Track
Created:
13-Jul-2000
Python-Version:
2.0
Post-History:
27-Jul-2000

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

서론

이 PEP는 ‘록스텝 반복’ 제안을 설명합니다. 이 PEP는 Python 2.0에 도입될 예정인 이 기능의 상태와 소유권을 추적합니다. 이 문서에는 기능에 대한 설명과 해당 기능을 지원하는 데 필요한 변경 사항의 개요가 포함되어 있습니다. 이 PEP는 메일링 리스트 포럼에서 진행된 논의를 요약하고, 적절한 경우 추가 정보를 위한 URL을 제공합니다. 이 파일의 CVS 개정 이력에는 확정적인 역사 기록이 포함되어 있습니다.

동기

Python의 표준 for 루프는 시퀀스가 모두 소진될 때까지 시퀀스의 모든 요소를 반복 처리합니다 [1]. 그러나 for 루프는 하나의 시퀀스만 반복 처리하므로, 둘 이상의 시퀀스를 록스텝 방식으로 반복 처리하는 것이 바람직한 경우가 많습니다. 다시 말해, 루프를 i번째 반복할 때 각 시퀀스의 i번째 요소를 포함하는 객체를 반환하는 방식입니다.

이를 수행하는 데 사용되는 일반적인 관용구는 직관적이지 않습니다. 이 PEP는 zip이라는 새로운 내장 함수를 도입하여 이러한 반복을 수행하는 표준 방법을 제안합니다.

zip()의 주된 동기는 록스텝 반복에서 비롯되지만, zip()을 내장 함수로 구현하면 for 루프 이외의 컨텍스트에서도 추가적인 유용성을 제공합니다.

록스텝 For 루프

록스텝 for 루프는 두 개 이상의 시퀀스를 대상으로 하는 비중첩 반복으로, 루프를 한 번 통과할 때마다 각 시퀀스에서 하나의 요소를 가져와 대상을 구성합니다. 이 동작은 이미 Python에서 map() 내장 함수를 사용하여 수행할 수 있습니다:

>>> a = (1, 2, 3)
>>> b = (4, 5, 6)
>>> for i in map(None, a, b): print i
...
(1, 4)
(2, 5)
(3, 6)
>>> map(None, a, b)
[(1, 4), (2, 5), (3, 6)]

for 루프는 이 목록을 평소처럼 단순히 반복 처리합니다.

map() 관용구는 Python에서 흔히 사용되지만, 몇 가지 단점이 있습니다.

  • 함수형 프로그래밍 배경이 없는 프로그래머에게는 명확하지 않습니다.
  • 마법의 None을 첫 번째 인자로 사용하는 것이 명확하지 않습니다.
  • 목록의 길이가 같지 않을 때 임의적이고, 의도하지 않은 경우가 많으며, 유연하지 않은 의미를 가집니다. 더 짧은 시퀀스는 None으로 채워집니다:
    >>> c = (4, 5, 6, 7)
    >>> map(None, a, c)
    [(1, 4), (2, 5), (3, 6), (None, 7)]
    

이러한 이유로 Python 2.0 베타 기간에는 록스텝 for 루프를 구문적으로 지원하기 위한 여러 제안이 제시되었습니다. 다음 두 가지 제안입니다:

for x in seq1, y in seq2:
  # stuff
for x, y in seq1, seq2:
  # stuff

이 두 형식은 모두 Python에서 이미 다른 의미를 가지므로 작동하지 않으며, 그 의미를 변경하면 기존 코드가 손상됩니다. 새 구문에 대한 다른 모든 제안도 같은 문제를 겪었거나, ‘리스트 컴프리헨션’이라는 또 다른 제안 기능과 충돌했습니다(PEP 202 참조).

제안된 해결책

제안된 해결책은 __builtin__ 모듈에서 사용할 수 있는 새로운 내장 시퀀스 제너레이터 함수를 도입하는 것입니다. 이 함수의 이름은 zip이며 다음 시그니처를 가집니다:

zip(seqa, [seqb, [...]])

zip()은 하나 이상의 시퀀스를 받아 요소들을 서로 엮습니다. 이는 같은 길이의 시퀀스에 대해 map(None, ...)이 수행하는 작업과 같습니다. 가장 짧은 시퀀스가 소진되면 엮기가 중지됩니다.

반환 값

zip()map()과 같은 방식으로 실제 Python 리스트를 반환합니다.

예제

다음은 아래의 참조 구현을 기반으로 한 몇 가지 예제입니다.:

>>> a = (1, 2, 3, 4)
>>> b = (5, 6, 7, 8)
>>> c = (9, 10, 11)
>>> d = (12, 13)

>>> zip(a, b)
[(1, 5), (2, 6), (3, 7), (4, 8)]

>>> zip(a, d)
[(1, 12), (2, 13)]

>>> zip(a, b, c, d)
[(1, 5, 9, 12), (2, 6, 10, 13)]

시퀀스의 길이가 같을 때 zip()은 되돌릴 수 있다는 점에 유의하십시오.:

>>> a = (1, 2, 3)
>>> b = (4, 5, 6)
>>> x = zip(a, b)
>>> y = zip(*x) # alternatively, apply(zip, x)
>>> z = zip(*y) # alternatively, apply(zip, y)
>>> x
[(1, 4), (2, 5), (3, 6)]
>>> y
[(1, 2, 3), (4, 5, 6)]
>>> z
[(1, 4), (2, 5), (3, 6)]
>>> x == z
1

시퀀스의 길이가 모두 같지 않으면 이런 방식으로 zip을 되돌릴 수 없습니다.

참조 구현

다음은 Python으로 작성한 zip() 내장 함수의 참조 구현입니다. 최종 승인이 이루어진 후 C 구현으로 대체됩니다.:

def zip(*args):
    if not args:
        raise TypeError('zip() expects one or more sequence arguments')
    ret = []
    i = 0
    try:
        while 1:
            item = []
            for s in args:
                item.append(s[i])
            ret.append(tuple(item))
            i = i + 1
    except IndexError:
        return ret

BDFL의 선언

참고: BDFL은 Guido van Rossum, 즉 Python의 ‘평생 자비로운 독재자’를 가리킵니다.

  • 함수의 이름입니다. 이 PEP의 이전 버전에는 zip()의 제안된 대체 이름을 20개 넘게 나열한 미해결 문제가 포함되어 있었습니다. 압도적으로 더 나은 선택지가 없는 상황에서 BDFL은 Haskell [2]의 계보 때문에 zip()을 강력히 선호합니다. 대체 이름 목록은 이 PEP의 버전 1.7을 참조하십시오.
  • zip()은 내장 함수여야 합니다.
  • 선택적 패딩입니다. 이 PEP의 이전 버전에서는 인자 시퀀스의 길이가 같지 않을 때 사용할 선택적인 pad 키워드 인자를 제안했습니다. 사용자가 pad 객체를 지정할 수 있다는 점을 제외하면 map(None, ...)의 의미와 유사한 동작입니다. KISS 원칙에 따라 가장 짧은 시퀀스에 맞춰 항상 잘라 내는 방안이 선호되었으므로 BDFL은 이를 거부했습니다. 실제로 필요하다면 나중에 추가하는 편이 더 쉽습니다. 필요하지 않다면 나중에 삭제하는 것조차 여전히 불가능할 것입니다.
  • 지연 평가입니다. 이 PEP의 이전 버전에서는 __getitem__()프로토콜을 사용하여 지연 평가를 수행하는 내장 객체를 zip()이 반환하도록 제안했습니다. BDFL은 실제 Python 리스트를 반환하는 방안을 선호하여 이를 강력히 거부했습니다. 향후 지연 평가가 필요해지면 BDFL은 xzip()함수를 추가할 것을 제안합니다.
  • 인수가 없는 zip()입니다. BDFL은 이것이 TypeError 예외를 발생시키기를 강력히 선호합니다.
  • 인수가 하나인 zip()입니다. BDFL은 이것이 1-튜플로 이루어진 리스트를 반환하기를 강력히 선호합니다.
  • 내부 및 외부 컨테이너 제어입니다. 이 PEP의 이전 버전에는 일부 사람들이 원했던 기능, 즉 내부 및 외부 컨테이너 유형을 제어하는 기능에 대한 상당히 긴 논의가 포함되어 있었습니다(이 PEP 버전에서는 각각 튜플과 리스트입니다). API와 구현이 단순화되었으므로 이 내용은 거부되었습니다. 더 자세한 분석은 이 PEP의 버전 1.7을 참조하십시오.

zip()에 대한 후속 변경

Python 2.4에서 인자 없이 호출된 zip()은 TypeError 예외를 일으키는 대신 빈 리스트를 반환하도록 수정되었습니다. 원래 동작의 근거는 인자가 없는 것이 프로그래밍 오류를 나타낸다고 여겨졌기 때문입니다. 하지만 그런 생각은 가변 길이 인자 목록의 언패킹을 위해 * 연산자와 함께 zip()을 사용하는 경우를 예상하지 못했습니다. 예를 들어, zip의 역함수는 다음과 같이 정의할 수 있습니다: unzip = lambda s: zip(*s). 이 변환은 튜플 리스트로 정의된 테이블에 대해 행렬 전치나 이와 동등한 행/열 교환도 정의합니다. 후자의 변환은 레코드를 행으로, 필드를 열로 하는 데이터 파일을 읽을 때 흔히 사용됩니다. 예를 들어, 다음 코드는:

date, rain, high, low = zip(*csv.reader(file("weather.csv")))

열 단위 데이터를 재배열하여 각 필드를 개별 튜플로 모음으로써 간단한 순회와 집계를 할 수 있게 합니다:

print "Total rainfall", sum(rain)

zip(*[])를 예외가 아니라 허용 가능한 경우로 처리하면 zip(*args)를 사용하는 코드를 더 쉽게 작성할 수 있습니다. 이는 데이터가 레코드 없는 null 케이스로부터 구축되거나 그 케이스로 재귀적으로 축소될 때 특히 유용합니다.

이러한 가능성을 보고, BDFL은 (다소 걱정하면서도) Py2.4에서 이 동작을 변경하는 데 동의했습니다.

기타 변경 사항

  • 위에서 설명한 xzip() 함수는 Py2.3의 itertools 모듈에 itertools.izip()로 구현되었습니다. 이 함수는 지연 동작을 제공하여, 한 번에 하나의 요소를 소비하고 한 번의 패스마다 하나의 튜플을 생성합니다. 이 “즉시(just-in-time)” 방식은 메모리를 절약하고, 리스트 기반의 대응물인 zip()보다 더 빠르게 실행됩니다.
  • itertools 모듈에는 itertools.repeat()itertools.chain()도 추가되었습니다. 이 도구들은 함께 사용하여 시퀀스를 None으로 채울 수 있습니다(map(None, seqn)의 동작과 일치시키기 위함입니다).:
    zip(firstseq, chain(secondseq, repeat(None)))
    

참고 자료

몇몇 CS 대학원생을 대상으로 한 Greg Wilson의 제안된 문법에 관한 설문 조사 http://www.python.org/pipermail/python-dev/2000-July/013139.html