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:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 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(즉, 내장 타입 “int”)의 인스턴스를 인자로 하여 내장 함수 “iter”를 호출하면 반환되는, types.intType을 위한 이터레이터를 정의합니다.
반환되는 이터레이터는 다음과 같이 동작합니다.
- 객체 i가
types.intType(내장 타입 int)의 인스턴스이고 i > 0이라고 가정합니다. iter(i)는 이터레이터 객체를 반환합니다.- 이 이터레이터 객체는 int 시퀀스 0,1,2,…,i-1을 순회합니다.
예시
iter(5)는 int 시퀀스 0,1,2,3,4를 순회하는 이터레이터 객체를 반환합니다. - 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차원 “테이블” 객체가 있을 수 있습니다. 테이블의 구현에 따라서는 먼저 각 행에, 그다음 각 열에 개별 객체로 접근하는 것이 불가능할 수도 있습니다. 오히려 행 인덱스와 열 인덱스를 사용하여 테이블의 셀에 접근하는 것이 가능할 수도 있습니다. 이러한 경우에는 테이블에서 원하는 항목에 접근하기 위해 인덱스(indices, 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))처럼 사용할 때 다루기 번거롭습니다 - 다른 언어의 동등한 메커니즘만큼 편리하지 않습니다,
- 성가시고, 이른바 “사마귀”(wart) 같은 결점입니다, 등등.
그리고 때때로 Python이 이 관용구에 대해 더 나은 메커니즘을 제공할 수 있는 방안에 대한 제안이 제출됩니다. 최근의 예로는 PEP 204 “Range Literals”(범위 리터럴)와 PEP 212 “Loop Counter Iteration”(루프 카운터 반복)이 있습니다.
대부분의 경우 이러한 제안에는 Python 문법 변경과 그 밖의 “무거운” 변경이 포함됩니다.
여기서 어려움의 일부는, 새로운 문법을 옹호하는 것이 곧 다음과 같은 측면을 포함해야 하는 “일반적인 인덱싱”에 대한 포괄적인 해법을 뜻한다는 점입니다:
- 시작 인덱스 값
- 끝 인덱스 값
- 스텝 값
- 열린 구간 대 닫힌 구간 대 반열린 구간
포괄적이고, 단순하고, 일반적이고, 파이썬다우며, 많은 사람에게 매력적이고, 구현하기 쉽고, 기존 구조와 충돌하지 않고, 기존 구조를 과도하게 오버로딩하지 않는 등의 조건을 갖춘 새로운 문법을 찾는 것은 예상보다 더 어려운 것으로 드러났습니다.
이 PEP에서 설명하는 제안은 (Python 2.1부터) 이미 사용할 수 있는 검증된 메커니즘, 즉 이터레이터를 사용하여 가장 흔한 경우에 도움이 되는 간단하고 “가벼운” 해결책을 제시함으로써 이 문제를 해결하고자 합니다.
Python 2.1부터 for 루프는 이미 “이터레이터” 프로토콜을 사용하므로, 이 PEP에서 제안하는 대로 types.IntType에 이터레이터를 추가하면 인덱스 for 루프 관용구에 대해 다음과 같은 축약형을 기본으로 사용할 수 있게 됩니다.:
for rowcount in table.getRowCount():
print table.getValueAt(rowcount, 0)
range() 또는 xrange() 함수를 사용하는 현재 메커니즘과 비교하여 이 접근 방식의 이점으로 주장되는 것은 다음과 같습니다:
- 더 단순합니다,
- 덜 어수선하며,
- 부차적인 구현 지향적 함수(
range()와xrange())에 의존할 필요 없이 당면한 문제에 집중합니다
그리고 다른 변경 제안들과 비교하면:
- 새로운 문법이 필요하지 않음
- 새로운 키워드가 필요하지 않습니다
- 새롭고 잘 확립된 이터레이터 메커니즘을 활용합니다
그리고 전반적으로:
- 리스트, 튜플, 딕셔너리, 문자열, 파일 등 다른 내장 타입에 대해 (Python 2.1 기준으로) 이미 포함된 이터레이터 기반 “편의” 변경 사항과 일관성이 있습니다.
하위 호환성
제안된 메커니즘은 새로운 문법도 새로운 키워드도 요구하지 않으므로 대체로 하위 호환성을 유지합니다. 기존의 유효한 모든 Python 프로그램은 수정 없이 계속 동작할 것입니다.
그러나 현재는 유효하지 않은 특정 문장이 이 제안에 따르면 유효해진다는 점에서, 이 제안은 완벽하게 하위 호환되지는 않습니다.
Tim Peters는 그러한 예 두 가지를 지적했습니다:
range()나xrange()를 넣는 것을 잊어버리는 흔한 경우로, 예를 들어:for rowcount in table.getRowCount(): print table.getValueAt(rowcount, 0)
위 코드는 Python 2.2에서 TypeError 예외를 발생시킵니다.
현재 제안에 따르면 위 문장은 유효하며 (아마도) 의도한 대로 동작할 것입니다. 아마도 이는 좋은 일일 것입니다.
Tim이 지적했듯이, 이는 “range를 잊어버리는” 실수의 흔한 경우입니다(현재는
range()나xrange()호출을 추가하여 바로잡습니다).- 튜플 언패킹을 사용할 때 오타를 내는, (바라건대) 매우 드문 경우입니다. 예를 들어:
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에 대응한다는 것이 명확합니까?응답: 앞서 언급했듯이 Python에는 시퀀스의 인덱스를 0에서 시작하여 시퀀스 길이보다 1 작은 값의 인덱스에서 (그 인덱스를 포함하여) 끝내는 강력한 관례가 있으므로, 제안된 시퀀스는 Python 프로그래머에게 상당히 직관적이면서도 유용하고 실용적이라고 주장합니다. 더 중요한 것은, 이 관례는 한번 익히면 기억하기가 매우 쉽다는 주장입니다. range 함수의 문서 문자열이
range(n)과 길이가 n인 리스트의 인덱스 사이의 자연스럽고 유용한 연관성을 언급한다는 점에 유의하십시오.- 발생 가능한 모호성
for i in 10: print i
다음과 혼동될 수 있습니다
for i in (10,): print i
응답: 이는 현재 Python에서 문자열의 경우와 정확히 같은 상황입니다(예를 들어 위의 예에서 10을 ‘spam’으로 바꿔 보십시오).
- 너무 일반적임: 최신 Python 릴리스에는 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”처럼 리터럴이 사용될 때 이를 좋아하지 않습니다. 이전 사안에 대한 답변에서 언급한 변형이 구현된다면, 이 사안은 논할 필요가 없습니다. 그렇지 않다면, 항상 TypeError를 발생시키는
__contains__메서드를 int 클래스에 정의하여 이 사안을 해결할 수도 있습니다. 그러면 “i in n”의 동작이 현재 Python의 동작과 동일해집니다. - 표준 “for item in collection:” 관용구가 분명히 더 나은 경우에도 초보자들이 인덱스를 사용하는 for 루프 관용구를 쓰지 않게 만들 수 있습니다.
답변: 표준 관용구는 들어맞는 경우에는 매우 훌륭하므로 별도의 “당근”도 “채찍”도 필요하지 않습니다. 반면에 다음과 같이 표준 관용구를 남용하거나 오용하는 경우가 눈에 띄기도 합니다(아마도 인덱스를 사용하는 for 루프 관용구가 어색하기 때문일 것입니다).:
for item in sequence: print sequence.index(item)
- 왜 더 큰 변경을 제안하지 않습니까?
PEP 276에 대한 반대 의견의 대부분은 정수 시퀀스를 지정하는 더 일반적인 문제를 해결하기 위해 Python에 훨씬 더 큰 변경을 가하는 쪽을 선호하는 사람들에게서 나왔습니다. 이들이 원하는 명세는 시퀀스의 시작값, 끝값, 증가값을 다룰 수 있을 만큼 일반적이면서 열린 구간, 닫힌 구간, 반열린(반닫힌) 구간 등 다양한 정수 구간도 다룰 수 있어야 합니다. 이러한 제안이 많이 논의되었습니다.
여기에는 다음이 포함됩니다.
- 리터럴 리스트 안에서 정수 시퀀스를 지정하기 위한 Haskell 방식의 표기법 추가,
- 시퀀스를 지정하기 위한 슬라이싱 표기법의 다양한 활용,
- 루프 헤더에서 관계 연산자를 사용할 수 있도록 for-in 루프의 문법을 변경하는 것,
- 정수 구간 객체에 “슬라이싱”을 제공하기 위해 관계 연산자나 나눗셈 연산자를 오버로드하는 메서드를 갖춘 정수 구간 클래스 생성,
- 그 밖의 여러 제안.
많은 논쟁이 있었지만 이러한 대규모 제안 중 어느 것도 압도적인 합의를 얻지 못했다는 점에 유의해야 합니다.
분명히 PEP 276은 그러한 대규모 변경을 제안하지 않으며, 대신 특정 문제 영역에 초점을 맞춥니다. 논의 기간이 끝나갈 무렵, 여러 게시자가 제시된 더 야심 찬 제안들보다 PEP 276의 좁은 초점과 단순함을 선호한다는 의견을 밝혔습니다. 그러한 대규모 대안 제안에는 어느 것이든 별도의 PEP가 필요하다는 데에는 합의가 있었던 것으로 보입니다. 이러한 인식에 따라 여기서는 여러 대안 제안의 세부 사항을 더 이상 논의하지 않습니다.
구현
현재 구현은 없지만 구현은 간단할 것으로 예상됩니다. 다만 저자는 이 제안의 아이디어를 시험해 보기 위한 수단으로 (Python으로 작성한) __iter__ 메서드를 가진 int의 서브클래스를 구현했습니다.
Copyright
This document has been placed in the public domain.