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

Python 개선 제안 한국어 번역

PEP 276 – int을 위한 단순 이터레이터

Author:
Jim Althoff <james_althoff at i2.com>
Status:
Rejected
Type:
Standards Track
Created:
12-Nov-2001
Python-Version:
2.3
Post-History:


Table of Contents

번역·라이선스 안내

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

개요

Python 2.1에서는 이터레이터를 지원하기 위한 새로운 기능이 추가되었습니다(PEP 234). 이터레이터는 많은 코딩 상황에서 유용하고 편리하다는 것이 입증되었습니다. Python의 for 루프 제어 구조 구현이 릴리스 2.1부터 이터레이터 프로토콜을 사용한다는 점에 주목할 필요가 있습니다. 또한 Python은 리스트, 튜플, 딕셔너리, 문자열, 파일과 같은 다음의 내장 타입에 대해 이터레이터를 제공한다는 점에도 주목할 필요가 있습니다. 이 PEP는 내장 타입 int(types.IntType)에 대한 이터레이터 추가를 제안합니다. 이러한 이터레이터는 Python에서 특정 for 루프의 코딩을 단순화할 것입니다.

BDFL 선언

이 PEP는 2005년 6월 17일 python-dev에 대한 메모와 함께 거부되었습니다.

원래의 요구 사항 대부분은 Python 2.3에서 채택된 enumerate() 함수로 충족되었습니다.

또한 이 제안은 다음과 같은 오용을 허용하고 조장하기까지 했습니다:

>>> for i in 3: print i
0
1
2

마찬가지로, 이 제안이 다음과 같은 문장에서 구문 오류를 비활성화한다는 점도 도움이 되지 않았습니다:

x, = 1

명세

types.intType의 인스턴스를 인자로 하여 내장 함수 “iter”를 호출했을 때 반환되는, types.intType(즉, 내장 타입 “int”)을 위한 이터레이터를 정의합니다.

반환된 이터레이터는 다음과 같은 동작을 합니다:

  • 객체 i가 types.intType의 인스턴스(내장 타입 int)이고 i > 0이라고 가정합니다
  • iter(i)는 이터레이터 객체를 반환합니다
  • 해당 이터레이터 객체는 0,1,2,…,i-1의 int 시퀀스를 순회합니다

    예:

    iter(5)는 0,1,2,3,4의 int 시퀀스를 순회하는 이터레이터 객체를 반환합니다
  • i <= 0이면, iter(i)는 “빈” 이터레이터, 즉 “next” 메서드의 첫 호출에서 StopIteration을 발생시키는 이터레이터를 반환합니다

다시 말해, 해당 이터레이터의 조건과 의미는 range()xrange() 함수의 조건 및 의미와 일치합니다.

int i와 연관된 시퀀스 0,1,2,…,i-1은 Python 시퀀스의 내장 인덱싱 프로토콜과 일치하기 때문에 Python 프로그래밍 맥락에서 “자연스러운” 것으로 간주된다는 점에 유의하십시오. 예를 들어 Python의 리스트와 튜플은 (양의 인덱스를 사용할 때) 0에서 시작해 len(object)-1로 끝나도록 인덱싱됩니다. 다시 말해, 그러한 객체는 0,1,2,…,len(object)-1의 시퀀스로 인덱싱됩니다

근거

흔한 프로그래밍 관용구는 객체 컬렉션을 가져와 정해진 순차적 순서로 컬렉션의 각 항목에 어떤 연산을 적용하는 것입니다. Python은 이 흔한 관용구를 처리하기 위해 “for in” 루프 제어 구조를 제공합니다. 그러나 각 인덱스를 순회하며 해당 인덱스를 사용해 컬렉션의 각 항목에 접근함으로써, “인덱스가 매겨진” 컬렉션의 각 항목에 접근해야 하는(혹은 그렇게 하는 것이 더 편리한) 경우가 발생합니다.

예를 들어, 테이블의 각 행의 첫 번째 열에 어떤 연산을 적용해야 하는 2차원 “테이블” 객체가 있을 수 있습니다. 테이블의 구현 방식에 따라 각 행과 각 열을 개별 객체로서 먼저 접근하는 것이 불가능할 수도 있습니다. 오히려 행 인덱스와 열 인덱스를 사용하여 테이블의 셀에 접근하는 것이 가능할 수 있습니다. 이런 경우에는 테이블에서 원하는 항목에 접근하기 위해 인덱스(indexes)의 시퀀스를 순회하는 관용구를 사용해야 합니다. (Java-Swing-Jython에서 흔히 사용되는 DefaultTableModel 클래스가 바로 이 프로토콜을 가지고 있다는 점에 유의하십시오.)

또 다른 흔한 예로는 둘 이상의 컬렉션을 병렬로 처리해야 하는 경우가 있습니다. 또 다른 예로는, 예를 들어 컬렉션의 매 두 번째 항목에 접근해야 하는 경우가 있습니다.

컬렉션 내 항목에 대한 접근이 인덱스에 대한 계산을 통해 이루어짐으로써, 항목 자체에 직접 접근하기보다는 인덱스에 접근해야 하는 경우가 그 밖에도 많이 있습니다.

이 관용구를 “인덱스 기반 for-루프” 관용구라고 부릅시다. 일부 프로그래밍 언어는 이 관용구를 처리하기 위한 내장 구문을 제공합니다. Python에서 인덱스 기반 for-루프 관용구를 구현하는 일반적인 관례는 내장 range()또는 xrange() 함수를 사용하여 인덱스의 시퀀스를 생성하는 것으로, 예를 들면 다음과 같습니다:

for rowcount in range(table.getRowCount()):
    print table.getValueAt(rowcount, 0)

또는

for rowcount in xrange(table.getRowCount()):
    print table.getValueAt(rowcount, 0)

때때로 Python 커뮤니티에서는 인덱스 기반 for-루프 관용구에 대한 논의가 있습니다. 이 설계 관용구를 위해 range()또는 xrange() 함수를 사용해야 하는 필요성이 다음과 같다고 주장되기도 합니다:

  • 명확하지 않다(Python을 처음 접하는 프로그래머에게),
  • 오류가 발생하기 쉽다(숙련된 Python 프로그래머조차 잊어버리기 쉽다)
  • xrange()range()의 차이와 권장 사용법을 이해해야 한다고 느끼는 사람들에게 혼란스럽고 주의를 산만하게 한다
  • 특히 len() 함수와 결합될 때, 즉 xrange(len(sequence))처럼 사용될 때 다루기 번거롭다
  • 다른 언어의 동등한 메커니즘만큼 편리하지 않다,
  • 성가시다, “결함”이다, 등등.

그리고 때때로 Python이 이 관용구에 대해 더 나은 메커니즘을 제공할 수 있는 방법에 대한 제안들이 제기됩니다. 최근의 예로는 PEP 204(“범위 리터럴”)과 PEP 212(“루프 카운터 이터레이션”)이 있습니다.

대부분의 경우, 이러한 제안에는 Python의 구문 변경 및 기타 “무거운” 변경 사항이 포함됩니다.

여기서 어려움의 일부는 새로운 구문을 주장하는 것이 “일반적인 인덱싱”에 대한 포괄적인 해결책을 암시하며, 이는 다음과 같은 측면들을 포함해야 한다는 점입니다:

  • 시작 인덱스 값
  • 끝 인덱스 값
  • 스텝 값
  • 열린 구간 대 닫힌 구간 대 반열린 구간

포괄적이고, 단순하고, 일반적이며, Pythonic하고, 많은 사람에게 매력적이며, 구현하기 쉽고, 기존 구조와 충돌하지 않으며, 기존 구조에 과도한 부담을 주지 않는 등의 새로운 구문을 찾는 것은 예상보다 더 어려운 것으로 드러났습니다.

이 PEP에서 개괄하는 제안은 이미 사용 가능한(Python 2.1 기준) 검증된 메커니즘, 즉 이터레이터를 사용하여 가장 흔한 경우를 돕는 단순하고 “가벼운” 해결책을 제시함으로써 이 문제를 해결하고자 합니다.

for-루프는 Python 2.1 기준으로 이미 “이터레이터” 프로토콜을 사용하고 있으므로, 이 PEP에서 제안하는 대로 types.IntType에 대한 이터레이터를 추가하면 인덱스 기반 for-루프 관용구에 대해 기본적으로 다음과 같은 단축 표기를 사용할 수 있게 됩니다:

for rowcount in table.getRowCount():
    print table.getValueAt(rowcount, 0)

range()또는 xrange() 함수를 사용하는 현재 메커니즘과 비교하여 이 접근 방식이 갖는 이점은 다음과 같다고 주장됩니다:

  • 더 단순함,
  • 덜 복잡하며,
  • 부차적인 구현 지향적 함수(range()xrange())에 의존할 필요 없이 당면한 문제에 집중합니다

그리고 다른 변경 제안들과 비교하면:

  • 새로운 구문이 필요하지 않습니다
  • 새로운 키워드가 필요하지 않습니다
  • 새롭고 잘 확립된 이터레이터 메커니즘을 활용합니다

그리고 일반적으로:

  • 리스트, 튜플, 딕셔너리, 문자열, 파일 등 다른 내장 타입들에 대해 (Python 2.1 기준으로) 이미 포함된 이터레이터 기반 “편의” 변경 사항과 일관성이 있습니다.

하위 호환성

제안된 메커니즘은 새로운 구문이나 새로운 키워드를 요구하지 않으므로 대체로 하위 호환됩니다. 기존의 유효한 모든 Python 프로그램은 수정 없이 계속 동작해야 합니다.

그러나 이 제안은, 현재는 유효하지 않은 특정 문장들이 현재 제안 하에서는 유효해진다는 의미에서 완벽하게 하위 호환되지는 않습니다.

Tim Peters는 그러한 예시 두 가지를 지적했습니다:

  1. 예를 들어 range()xrange()를 포함하는 것을 잊어버리는 흔한 경우:
    for rowcount in table.getRowCount():
        print table.getValueAt(rowcount, 0)
    

    Python 2.2에서는 TypeError 예외를 발생시킵니다.

    현재 제안 하에서는 위 문장이 유효하며 (아마도) 의도한 대로 동작할 것입니다. 짐작건대, 이는 좋은 일입니다.

    Tim이 지적했듯이, 이는 “잊어버린 range” 실수의 흔한 경우입니다(현재는 range()xrange() 호출을 추가하여 수정합니다).

  2. 튜플 언패킹을 사용할 때 오타를 내는 (바라건대) 매우 드문 경우입니다. 예를 들어:
    x, = 1
    

    Python 2.2에서는 TypeError 예외를 발생시킵니다.

    현재 제안 하에서는 위 문장이 유효하며 x를 0으로 설정할 것입니다. PEP 저자는 이 타이핑 오류가 얼마나 흔한지, 또한 현재 제안 하에서 그러한 오류를 잡아내기가 얼마나 어려울지에 대한 데이터를 가지고 있지 않습니다. 그는 이런 일이 자주 발생하지는 않으며, 발생하더라도 비교적 쉽게 수정할 수 있을 것이라고 짐작합니다.

쟁점

Python interest 메일링 리스트에서 PEP 276에 관한 광범위한 논의는 다양한 의견을 시사합니다: 찬성하는 사람도 있고, 중립적인 사람도 있고, 반대하는 사람도 있습니다. 찬성하는 이들은 대체로 정수를 위한 단순한 이터레이터의 유용성, 편의성, 배우기 쉬움, 단순함에 관한 위의 주장에 동의하는 경향이 있습니다.

다음은 PEP 276의 문제점입니다:

  • range/xrange를 그대로 사용하는 것으로 충분합니다.

    응답: 일부 게시자는 이렇게 느낍니다. 다른 이들은 동의하지 않습니다.

  • 일부는 정수 n에 대해 “0, 1, 2, …, n-1” 시퀀스를 순회하는 것이 직관적이지 않다고 느낍니다. 예를 들어 “for i in 5:”는 (일부에게) “명백하지 않은” 것으로 간주됩니다. 일부는 이 사용법이 “올바른 느낌”을 주지 않기 때문에 이를 싫어합니다. 일부는 이러한 유형의 사용법이 정수를 시퀀스로 간주하도록 강제한다고 믿기 때문에 이를 싫어하며, 이것이 그들에게는 잘못된 것으로 느껴집니다. 일부는 for 루프가 임의의 이터레이터가 아니라 명시적인 시퀀스를 다루는 것으로 간주하는 것을 선호하기 때문에 이를 싫어합니다.

    응답: 일부는 제안된 관용구를 좋아하며 이를 단순하고 우아하며 배우기 쉽고 사용하기 쉬운 것으로 봅니다. 일부는 이 문제에 대해 중립적입니다. 앞서 언급했듯이 다른 이들은 이를 싫어합니다.

  • iter(5)가 시퀀스 0,1,2,3,4에 대응된다는 것이 명백합니까?

    응답: 앞서 언급했듯이 파이썬에는 시퀀스를 0부터 인덱싱하기 시작해서 시퀀스 길이보다 1 작은 값을 가진 인덱스에서 (포함하여) 멈추는 강력한 관례가 있으므로, 제안된 시퀀스는 파이썬 프로그래머에게 합리적으로 직관적이면서도 유용하고 실용적이라는 주장이 있습니다. 더 중요한 것은, 일단 익히고 나면 이 관례는 기억하기 매우 쉽다는 주장입니다. range 함수의 docstring이 range(n)과 길이가 n인 리스트의 인덱스 사이의 자연스럽고 유용한 연관성을 언급하고 있다는 점에 유의하십시오.

  • 발생 가능한 모호성
    for i in 10: print i
    

    다음과 혼동될 수 있습니다

    for i in (10,): print i
    

    응답: 이는 현재 파이썬의 문자열과 정확히 동일한 상황입니다(예를 들어 위 예제에서 10을 ‘spam’으로 바꿔 보십시오).

  • 지나치게 일반적임: 파이썬의 최신 릴리스에는 for 루프와 마찬가지로 이터레이터가 암묵적으로 호출되는 맥락이 존재합니다. 일부는 (for 루프를 제외한) 그러한 맥락 중 하나에서 정수에 대해 이터레이터가 호출되는 것이 예상치 못한 동작과 버그로 이어질 수 있다고 우려합니다. 앞서 언급한 “x, = 1” 예제가 바로 그러한 경우입니다.

    응답: 저자의 관점에서, PEP 276논의에서 확인된 위와 같은 예제들은 미묘하고 감지하기 어려운 오류로 이어질 만큼 실수로 오용될 것으로는 보이지 않았습니다.

    또한, 이 제안의 명세 섹션에 설명된 방식을 변형하여 이 문제를 다룰 방법이 있는 것으로 보입니다. int 클래스에 __iter__ 메서드를 추가하는 대신, for 루프 처리 코드를 변경하여 (본질적으로) 다음과 같이 변환합니다

    for i in n:  # when isinstance(n,int) is 1
    

    다음으로

    for i in xrange(n):
    

    이 접근 방식은 for 루프에서 __iter__ 메서드와 동일한 결과를 제공하지만, 다른 어떤 맥락에서도 정수 값에 대한 순회를 막습니다. 예를 들어 리스트와 튜플은 __iter__이 없으며 특수 코드로 처리됩니다. 정수 값도 하나의 특수한 경우가 될 것입니다.

  • “i in n”은 매우 부자연스러워 보입니다.

    응답: 일부는 “i in len(mylist)”가 쉽게 이해되고 유용할 것이라고 느낍니다. 일부는 특히 “i in 5”처럼 리터럴이 사용될 때 이를 싫어합니다. 앞선 문제에 대한 응답에서 언급된 변형이 구현된다면 이 문제는 무의미해집니다. 그렇지 않다면, int 클래스에 항상 TypeError를 발생시키는 __contains__ 메서드를 정의하여 이 문제를 다룰 수도 있습니다. 이렇게 하면 “i in n”의 동작이 현재 파이썬의 동작과 동일해질 것입니다.

  • 표준적인 “for item in collection:” 관용구가 명백히 더 나은 경우에도 초보자들이 인덱스 for 루프 관용구를 사용하는 것을 단념시킬 수 있습니다.

    답변: 표준 관용구는 적합할 때는 매우 훌륭해서 추가적인 “당근”도 “채찍”도 필요하지 않습니다. 반면, 표준 관용구의 남용/오용 사례가 눈에 띄는 것도 사실입니다(아마도 인덱스 기반 for-루프 관용구의 어색함 때문일 가능성이 큽니다), 예를 들면 다음과 같습니다.:

    for item in sequence:
        print sequence.index(item)
    
  • 왜 더 큰 변경을 제안하지 않았습니까?

반대 의견의 대다수는 정수 수열을 지정하는 더 일반적인 문제—즉 수열의 시작 값, 끝 값, 증가 값을 다룰 만큼 충분히 일반적이며 열린, 닫힌, 반열린(반닫힌) 정수 구간의 변형까지 다루는 지정 방식—를 해결하기 위해 파이썬에 훨씬 더 큰 변경을 가하는 쪽을 선호하는 사람들에게서 나온 것으로, PEP 276에 대한 것이었습니다. 그러한 제안이 여럿 논의되었습니다.

여기에는 다음이 포함됩니다.

  • 리터럴 리스트에서 정수 시퀀스를 지정하기 위한 하스켈과 유사한 표기법 추가,
  • 시퀀스를 지정하기 위한 슬라이싱 표기법의 다양한 활용,
  • 루프 헤더에서 관계 연산자의 사용을 허용하도록 for-in 루프의 문법을 변경,
  • 정수 구간 객체에 대한 “슬라이싱”을 제공하기 위해 관계 연산자나 나눗셈 연산자를 오버로드하는 메서드를 갖춘 정수 구간 클래스의 생성,
  • 그 밖에도 여러 가지입니다.

이러한 대규모 제안들에 대해서는 많은 논쟁이 있었지만 압도적인 합의에는 이르지 못했다는 점에 유의해야 합니다.

분명히, PEP 276은 이렇게 대규모의 변경을 제안하는 것이 아니라 특정 문제 영역에 초점을 맞추고 있습니다. 논의 기간이 끝나갈 무렵, 여러 게시자들이 제기되었던 더 야심 찬 제안들에 비해 PEP 276의 좁은 초점과 단순함을 선호한다는 뜻을 밝혔습니다. 그러한 더 큰 규모의 대안적 제안에 대해서는 PEP가 필요하다는 데 대한 합의가 있었던 것으로 보입니다. 이러한 인식에 비추어, 다양한 대안적 제안들의 세부 사항은 여기서 더 이상 논의하지 않습니다.

구현

현재 구현은 제공되지 않지만, 구현 자체는 간단할 것으로 예상됩니다. 그러나 저자는 이 제안의 아이디어를 시험해 보기 위한 수단으로, (Python으로 작성된) __iter__ 메서드를 가진 int의 서브클래스를 구현한 바 있습니다.