[Final] PEP 234 - Iterators
원문 링크: PEP 234 - Iterators
상태: Final 유형: Standards Track 작성일: 30-Jan-2001
PEP 234 – Iterators
- 작성자: Ka-Ping Yee, Guido van Rossum
- 상태: Final (최종)
- 유형: Standards Track
- 생성일: 2001년 1월 30일
- Python 버전: 2.1 (Python 2.2에 구현됨)
초록 (Abstract)
이 문서는 for
루프의 동작을 제어하기 위해 객체가 제공할 수 있는 이터레이션(iteration) 인터페이스를 제안합니다. 루프 동작은 이터레이터 객체를 생성하는 메서드를 제공함으로써 사용자 정의될 수 있습니다. 이터레이터는 호출될 때마다 시퀀스(sequence)의 다음 항목을 생성하는 get next value
연산을 제공하며, 더 이상 항목이 없을 때는 예외를 발생시킵니다.
또한, 딕셔너리의 키(keys)와 파일의 라인(lines)에 대한 특정 이터레이터를 제안하며, dict.has_key(key)
를 key in dict
로 표현할 수 있도록 하는 제안도 포함합니다.
참고: 이 PEP는 두 번째 저자(Guido van Rossum)에 의해 거의 전면적으로 다시 작성되었으며, Python 2.2 CVS 트렁크에 체크인된 실제 구현을 설명합니다. 초기 버전의 일부 난해한 제안들은 현재 철회되었으며, 나중에 별도의 PEP의 주제가 될 수 있습니다.
C API 명세 (C API Specification)
StopIteration
예외 정의: 이터레이션의 끝을 알리는 새로운 예외StopIteration
이 정의되었습니다.tp_iter
슬롯: 이터레이터를 요청하기 위한 새로운 슬롯tp_iter
가 타입(type) 객체 구조에 추가되었습니다. 이 슬롯은PyObject *
인수를 하나 받고PyObject *
를 반환하거나NULL
을 반환하는 함수여야 합니다. 이 슬롯을 사용하기 위해PyObject_GetIter()
라는 새로운 C API 함수가 추가되었습니다.tp_iternext
슬롯: 이터레이션의 다음 값을 얻기 위한 새로운 슬롯tp_iternext
가 타입 구조에 추가되었습니다. 이 슬롯을 사용하기 위해PyIter_Next()
라는 새로운 C API 함수가 추가되었습니다.tp_iternext
슬롯이NULL
을 반환하는 경우, 다음과 같은 세 가지 가능성이 있습니다.- 예외가 설정되지 않음: 이터레이션의 끝을 의미합니다.
StopIteration
예외(또는 파생 클래스)가 설정됨: 이터레이션의 끝을 의미합니다.- 다른 예외가 설정됨: 일반적인 오류 발생을 의미합니다.
상위 레벨 함수인
PyIter_Next()
는StopIteration
예외가 발생하면 이를 지우므로,NULL
반환 조건은 더 간단합니다.- 예외가 설정되지 않음: 이터레이션이 끝났음을 의미합니다.
- 예외가 설정됨: 오류가 발생했음을 의미하며, 일반적으로 전파되어야 합니다.
next()
메서드 자동 생성: C로 구현된 이터레이터는tp_iternext
슬롯과 유사한 의미를 가진next()
메서드를 직접 구현해서는 안 됩니다.PyType_Ready()
에 의해 타입의 딕셔너리가 초기화될 때,tp_iternext
슬롯의 존재는 해당 슬롯을 래핑(wrapping)하는next()
메서드가 타입의tp_dict
에 추가되도록 합니다.Py_TPFLAGS_HAVE_ITER
플래그: 바이너리 하위 호환성을 보장하기 위해tp_flags
필드에Py_TPFLAGS_HAVE_ITER
라는 새로운 플래그가 추가되었습니다.tp_iter
또는tp_iternext
슬롯에 접근하기 전에 이 플래그를 확인해야 합니다.- 시퀀스 객체를 위한 폴백(Fallback):
PyObject_GetIter()
함수는 인수가tp_iter
함수를 구현하지 않는 시퀀스일 경우, 폴백(fallback) 의미론을 구현합니다. 이 경우 가벼운(lightweight) 시퀀스 이터레이터 객체가 구성되어 시퀀스의 항목들을 자연스러운 순서로 이터레이트합니다. - 바이트코드 변경:
for
루프에 대해 생성되는 Python 바이트코드는 새로운 opcode인GET_ITER
와FOR_ITER
를 사용하도록 변경되었습니다. 이는 루프 변수의 다음 값을 가져오기 위해 시퀀스 프로토콜 대신 이터레이터 프로토콜을 사용합니다. - 자기 자신을 반환하는
tp_iter
: 이터레이터는tp_iter
슬롯이 자기 자신에 대한 참조를 반환하도록 구현해야 합니다. 이는for
루프에서 시퀀스가 아닌 이터레이터를 사용할 수 있게 하기 위해 필요합니다. StopIteration
이후 동작: 이터레이터 구현(C 또는 Python)은 이터레이터가 소진(exhaustion)을 알린 후에는tp_iternext
또는next()
메서드에 대한 후속 호출도 계속해서 소진을 알려야 함을 보장해야 합니다.
Python API 명세 (Python API Specification)
StopIteration
예외:StopIteration
예외는 표준 예외 중 하나로 노출되며,Exception
을 상속합니다.iter()
내장 함수:iter()
라는 새로운 내장 함수가 정의되었으며, 두 가지 방식으로 호출할 수 있습니다.iter(obj)
:PyObject_GetIter(obj)
를 호출합니다.iter(callable, sentinel)
:callable
을 호출하여 새로운 값을 생성하고, 반환 값을sentinel
값과 비교하는 특별한 종류의 이터레이터를 반환합니다. 반환 값이sentinel
과 같으면 이터레이션의 끝을 알리며StopIteration
이 발생합니다. 같지 않으면 다음 값으로 반환됩니다.callable
이 예외를 발생시키면 정상적으로 전파됩니다.
- 이터레이터 객체의
next()
메서드:iter()
함수에 의해 반환된 이터레이터 객체는next()
메서드를 가집니다. 이 메서드는 이터레이션의 다음 값을 반환하거나, 이터레이션의 끝을 알리기 위해StopIteration
예외를 발생시킵니다. 다른 예외는 오류를 나타내며 정상적으로 전파되어야 합니다. - 사용자 정의 이터러블 및 이터레이터:
- 클래스는
__iter__()
메서드를 정의하여 이터레이션 방식을 정의할 수 있습니다. 이 메서드는 추가 인수를 받지 않고 유효한 이터레이터 객체를 반환해야 합니다. - 이터레이터가 되고자 하는 클래스는 두 가지 메서드를 구현해야 합니다: 위에서 설명한 대로 동작하는
next()
메서드와self
를 반환하는__iter__()
메서드.
- 클래스는
- 두 가지 프로토콜:
__iter__()
또는__getitem__()
을 구현하는 객체는for
루프를 통해 이터레이트될 수 있습니다.next()
를 구현하는 객체는 이터레이터로 기능할 수 있습니다. 컨테이너류 객체는 일반적으로 첫 번째 프로토콜을 지원합니다. 이터레이터는 현재 두 프로토콜을 모두 지원해야 합니다.
딕셔너리 이터레이터 (Dictionary Iterators)
key in dict
구문: 딕셔너리는has_key()
메서드와 동일한 테스트를 구현하는sq_contains
슬롯을 구현합니다. 이는if k in dict:
와 같이 작성할 수 있음을 의미하며,if dict.has_key(k):
와 동등합니다.- 딕셔너리 키 이터레이션: 딕셔너리는 딕셔너리의 키를 효율적으로 이터레이트하는 이터레이터를 반환하는
tp_iter
슬롯을 구현합니다. 이 이터레이션 동안 딕셔너리는 수정되어서는 안 되지만, 기존 키의 값을 설정하는 것은 허용됩니다(삭제나 추가,update()
메서드는 허용되지 않음). 이는for k in dict:
와 같이 작성할 수 있음을 의미하며,for k in dict.keys():
보다 훨씬 빠릅니다. - 명시적 이터레이터 메서드: 딕셔너리에 명시적으로 다른 종류의 이터레이터를 반환하는 메서드들이 추가되었습니다.
for key in dict.iterkeys(): ...
(키 이터레이터)for value in dict.itervalues(): ...
(값 이터레이터)for key, value in dict.iteritems(): ...
(키-값 쌍 이터레이터)for x in dict
는for x in dict.iterkeys()
의 축약형입니다.
파일 이터레이터 (File Iterators)
파일 객체에 대한 이터레이터 제안은 파일의 라인(line)을 이터레이트하는 일반적인 관용구(idiom)가 보기 흉하고 느리다는 불만에 대한 좋은 해결책을 제공합니다.
- 파일 라인 이터레이션: 파일은
iter(f.readline, "")
와 동등한tp_iter
슬롯을 구현합니다. 이는for line in file: ...
와 같이 작성할 수 있음을 의미하며,while 1: line = file.readline(); if not line: break; ...
보다 빠릅니다. - 파괴적인(Destructive) 이터레이터: 일부 이터레이터는 파괴적(destructive)입니다. 즉, 모든 값을 소비하며, 동일한 값을 독립적으로 이터레이트하는 두 번째 이터레이터를 쉽게 생성할 수 없습니다. 파일을 다시 열거나
seek()
를 사용하여 처음으로 이동할 수 있지만, 파이프(pipe)나 스트림 소켓(stream socket)과 같은 일부 파일 유형에서는 이러한 해결책이 작동하지 않습니다. -
내부 버퍼링 및 제약 사항: 파일 이터레이터는 내부 버퍼를 사용하므로, 이를 다른 파일 연산(
file.readline()
등)과 혼합하면 올바르게 작동하지 않을 수 있습니다. 예를 들어, 두 개의 연속된for
루프에서 파일 이터레이터를 사용할 때, 첫 번째 루프가 읽어들인 버퍼를 두 번째 루프가 고려하지 않아 예상과 다르게 동작할 수 있습니다. 올바른 사용법은 이터레이터 객체를 변수에 할당하여 재사용하는 것입니다.it = iter(file) for line in it: if line == "\n": break for line in it: print(line)
이러한 제약 사항의 이유는
for line in file
이 파일을 라인별로 이터레이트하는 권장되고 표준적인 방법이 되어야 하며, 가능한 한 빨라야 하기 때문입니다. 이터레이터 버전은 이터레이터 내부 버퍼 덕분에readline()
을 호출하는 것보다 훨씬 빠릅니다.
제안 배경 (Rationale)
이 제안의 모든 부분이 포함되면, 일관되고 유연한 방식으로 많은 우려 사항을 해결합니다. 주요 장점은 다음과 같습니다.
- 확장 가능한 이터레이터 인터페이스를 제공합니다.
- 리스트(list) 이터레이션의 성능 향상을 가능하게 합니다.
- 딕셔너리(dictionary) 이터레이션의 큰 성능 향상을 가능하게 합니다.
- 요소에 대한 무작위 접근(random access)을 제공하는 척하지 않고, 단순히 이터레이션만을 위한 인터페이스를 제공할 수 있게 합니다.
- 시퀀스 및 매핑을 에뮬레이트하는 모든 기존 사용자 정의 클래스 및 확장 객체와 하위 호환됩니다.
- 시퀀스가 아닌 컬렉션(non-sequence collections)을 이터레이트하는 코드를 더 간결하고 읽기 쉽게 만듭니다.
해결된 문제 (Resolved Issues)
다음 주제들은 합의 또는 BDFL(Benevolent Dictator For Life, Guido van Rossum)의 결정에 따라 해결되었습니다.
next()
메서드 이름:next()
에 대한 두 가지 대안적 철자(__next__()
,__call__()
)가 제안되었지만 거부되었습니다.__next__()
반대 의견:for
루프에서 많이 사용되지만, 사용자 코드가next()
를 직접 호출할 수도 있으므로__next__()
는 보기 좋지 않습니다. 또한,prev()
,current()
,reset()
과 같은 작업으로 프로토콜을 확장할 가능성이 있는데, 이때__prev__()
,__current__()
,__reset__()
과 같은 이름은 원하지 않을 것입니다.__call__()
반대 의견 (원래 제안): 문맥을 벗어나면x()
는 읽기 어렵지만,x.next()
는 명확합니다. 모든 특수 목적 객체가 가장 일반적인 연산을 위해__call__()
을 사용하려 할 위험이 있으며, 이는 명확성보다 더 많은 혼란을 야기할 수 있습니다.- 결정:
next()
를 사용합니다. (회고적으로는__next__()
를 사용하고next(it)
와 같은 새로운 내장 함수를 두는 것이 더 좋았을 수도 있지만, Python 2.2에 이미 배포되어 너무 늦었습니다.)
- 이터레이터 재시작: 이터레이터를 재시작하는 기능이 요청되었지만, 이는 시퀀스에 대해
iter()
를 반복적으로 호출하여 처리해야 하며, 이터레이터 프로토콜 자체를 통해서는 처리하지 않기로 결정되었습니다. StopIteration
예외 비용:StopIteration
예외가 너무 비싸지 않냐는 의문이 제기되었습니다.StopIteration
예외에 대한 여러 대안(특수 값End
, 이터레이터가 끝났는지 테스트하는 함수end()
,IndexError
재사용)이 제안되었습니다.- 특수 값
End
의 문제점: 시퀀스가 그 특수 값을 포함할 경우, 루프가 경고 없이 조기에 종료될 수 있습니다. end()
함수 호출의 문제점: 이터레이션당 두 번의 호출이 필요하며, 이는 예외 테스트보다 훨씬 비쌉니다.IndexError
재사용의 문제점: 진정한 오류일 수 있는IndexError
가 루프를 조기에 종료함으로써 가려질 수 있어 혼란을 야기할 수 있습니다.- 결정:
StopIteration
예외를 사용합니다.
- 특수 값
- 표준 이터레이터 타입: 모든 이터레이터가 파생되어야 하는 표준 이터레이터 타입에 대한 요청이 있었지만 거부되었습니다. 이는 Python의 방식이 아니라고 판단되었습니다.
key in dict
의 의미:dict.has_key(x)
와 같은x in dict
의 해석이 가장 유용하다고 판단되었습니다.x in list
가 값의 존재 여부를 확인하는 반면,x in dict
가 키의 존재 여부를 확인하는 것에 대한 반대가 있었지만, 리스트와 딕셔너리 간의 대칭성이 약하므로 이 주장은 큰 의미가 없다고 결론지었습니다.iter()
이름:iter()
는 축약형이며iterate()
,traverse()
와 같은 대안이 제안되었지만 너무 길게 느껴졌습니다. Python은repr()
,str()
,len()
과 같이 일반적인 내장 함수에 축약형을 사용한 역사가 있습니다.- 결정:
iter()
를 사용합니다. - 두 가지 다른 연산(객체에서 이터레이터를 가져오는 것과 센티널 값을 가진 함수를 위한 이터레이터를 만드는 것)에 동일한 이름을 사용하는 것이 다소 보기 흉하다는 의견이 있었지만, 두 연산 모두 이터레이터를 반환하므로 기억하기 쉽다는 이유로 유지되었습니다.
- 결정: 내장 함수
iter()
는 찾을 센티널(sentinel) 값인 선택적 인수를 받습니다.
- 결정:
StopIteration
후next()
호출: 특정 이터레이터 객체가StopIteration
을 한 번 발생시킨 후, 후속next()
호출에서도 계속StopIteration
을 발생시켜야 하는지에 대한 논의가 있었습니다.- 결정:
StopIteration
이 발생한 후에는it.next()
를 호출해도 계속StopIteration
이 발생합니다. (Python 2.2에서는 구현되지 않았지만 Python 2.3에서 수정되었습니다.)
- 결정:
- 파일 객체가 자기 자신 이터레이터: 파일 객체가 자체적으로
next()
메서드를 가진 이터레이터가 되어야 한다는 제안이 있었지만, 이는 “끈적한 StopIteration (sticky StopIteration)” 기능을 구현하기 더 어렵게 만들 수 있다는 단점 때문에 잠정적으로 거부되었습니다. - 이터레이터 프로토콜 확장 (
prev()
,current()
,rewind()
등):prev()
,current()
,finished()
,rewind()
,__len__()
,position()
등 이터레이터 프로토콜 확장에 대한 요청이 있었지만, 많은 경우 임의의 버퍼링을 추가하지 않고는 쉽게 구현할 수 없거나 전혀 합리적으로 구현할 수 없기 때문에 거부되었습니다. for x in dict
의 의미:for x in dict:
가 딕셔너리의 키, 값 또는 항목 중 무엇을 할당해야 하는지에 대한 긴 논의가 있었습니다.if x in y
와for x in y
사이의 대칭성은 키를 이터레이트해야 함을 시사했습니다. 실용적인 관점에서dict.items()
와dict.keys()
사용이 거의 비슷하다는 점, 그리고dict.keys()
를 사용하는 많은 루프가 결국dict[x]
를 통해 해당 값을 사용하는 점을 들어 항목(키와 값)을 이터레이트하는 것이 더 많은 경우를 지원할 수 있다는 주장이 있었습니다. 하지만 Guido van Rossum은for x in dict
와if x in dict
사이의 일관성이 매우 중요하다고 판단했습니다.- 결정 (BDFL 결정):
for x in dict
는 키를 이터레이트하며, 딕셔너리는 다른 종류의 딕셔너리 이터레이터를 반환하기 위해iteritems()
,iterkeys()
,itervalues()
를 가집니다.for key, value in dict.iteritems():
를 사용하면 항목(items)에 대한 빠른 이터레이션이 가능합니다.
- 결정 (BDFL 결정):
이메일 목록 (Mailing Lists)
이터레이터 프로토콜은 SourceForge의 다음 메일링 리스트에서 광범위하게 논의되었습니다.
http://lists.sourceforge.net/lists/listinfo/python-iterators
- 초기에는 Yahoo에서 일부 논의가 이루어졌으며, 아카이브는 여전히 접근 가능합니다.
http://groups.yahoo.com/group/python-iter
저작권 (Copyright)
이 문서는 퍼블릭 도메인(public domain)에 있습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments