PEP 357 – 임의의 객체를 슬라이싱에 사용할 수 있도록 허용하기
- Author:
- Travis Oliphant <oliphant at ee.byu.edu>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 09-Feb-2006
- Python-Version:
- 2.5
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 PyNumberMethods에 nb_index 슬롯을 추가하고 __index__ 특수 메서드를 추가할 것을 제안합니다. 이를 통해 슬라이스 문법(이 슬롯의 이름이 여기에서 유래했습니다)처럼 Python에서 정수가 명시적으로 필요한 모든 곳에 임의의 객체를 사용할 수 있게 됩니다.
근거
현재 정수와 롱 정수는 슬라이스 문법에서 허용되는 유일한 객체라는 점에서 슬라이싱에서 특별한 역할을 합니다. 다시 말해, X가 시퀀스 프로토콜을 구현하는 객체라면 X[obj1:obj2]는 obj1과 obj2가 모두 정수 또는 롱 정수일 때만 유효합니다. obj1과 obj2가 자신이 시퀀스의 인덱스로 합리적으로 사용될 수 있다는 것을 Python에 알릴 방법이 없습니다. 이는 불필요한 제약입니다.
예를 들어 NumPy에는 8, 16, 32, 64비트의 부호 없는 정수와 부호 있는 정수에 대응하는 8가지 서로 다른 정수 스칼라가 있습니다. 이 타입 객체들은 Python이 진짜 정수를 기대하는 많은 곳에서 정수로 합리적으로 사용될 수 있지만, 메모리 레이아웃이 호환되지 않기 때문에 Python 정수 타입을 상속할 수 없습니다. 어떤 객체가 정수처럼 동작할 수 있다는 것을 Python에 알릴 방법이 있어야 합니다.
nb_int(및 __int__ 특수 메서드)는 객체를 정수로 강제 변환하는 데 사용되므로 이 목적으로 사용할 수 없습니다. 정수로 강제 변환될 수 있는 모든 객체를 Python이 진짜 정수를 기대하는 모든 곳에서 정수로 사용할 수 있도록 허용하는 것은 적절하지 않습니다. 예를 들어 슬라이싱에서 객체를 정수로 변환하는 데 __int__를 사용한다면, float 객체가 슬라이싱에 허용되어 x[3.2:5.8]이 마땅히 오류를 발생시켜야 함에도 발생시키지 않게 됩니다.
제안
PyNumberMethods에 nb_index 슬롯을 추가하고, 이에 대응하는 __index__ 특수 메서드를 추가합니다. 객체는 Python 정수(int 또는 long)를 반환하는 함수를 정의하여 nb_index 슬롯에 넣을 수 있습니다. 그러면 이 정수는 PySequence_GetSlice, PySequence_SetSlice, PySequence_DelSlice처럼 Python이 Py_ssize_t 값을 필요로 할 때마다 적절히 그 값으로 변환될 수 있습니다.
명세
nb_index슬롯은 다음과 같은 시그니처를 가집니다:PyObject *index_func (PyObject *self)
반환되는 객체는 Python
IntType또는 PythonLongType이어야 합니다. 오류가 발생하면 적절한 오류를 설정한 채 NULL을 반환해야 합니다.__index__특수 메서드는 다음과 같은 시그니처를 가집니다:def __index__(self): return obj
여기서 obj는 int 또는 long이어야 합니다.
- 3개의 새로운 추상 C-API 함수가 추가됩니다
- 첫 번째 함수는 객체가 index 슬롯을 지원하는지, 그리고 그 슬롯이 채워져 있는지 검사합니다.
int PyIndex_Check(obj)
이 함수는 객체가
nb_index슬롯을 정의하면 참을 반환합니다. - 두 번째 함수는
nb_index호출을 감싸는 간단한 래퍼로, 호출할 수 없거나 호출이 int 또는 long을 반환하지 않으면PyExc_TypeError를 발생시킵니다.PyIndex_Check가PyNumber_Index호출 내부에서 수행되므로, 호환성을 먼저 검사하는 대신 이 함수를 직접 호출하고 발생하는 오류를 처리할 수 있습니다.PyObject *PyNumber_Index (PyObject *obj)
- 세 번째 함수는 인덱싱이나 기타 용도로 사용하기 위해 객체에서 실제로
Py_ssize_t값을 얻어야 하는 흔한 상황을 처리하는 데 도움을 줍니다.Py_ssize_t PyNumber_AsSsize_t(PyObject *obj, PyObject *exc)
이 함수 호출 시 obj에
nb_index슬롯이 있으면 해당 슬롯을 호출한 다음, 반환된 Python 정수를Py_ssize_t값으로 변환합니다. 변환에 성공하면 그 값을 반환합니다. 두 번째 인자로는nb_index에서 반환된 정수가Py_ssize_t값에 들어가지 않을 때 어떻게 처리할지 제어할 수 있습니다.exc가 NULL이면, obj의
nb_index슬롯이 양의 정수를 반환했는지 음의 정수를 반환했는지에 따라 반환값이PY_SSIZE_T_MAX또는PY_SSIZE_T_MIN으로 잘립니다. exc가 NULL이 아니면, 이는 Python 정수나 long을Py_ssize_t로 변환할 때 발생한PyExc_OverflowError를 대체하도록 설정될 오류 객체입니다.
- 첫 번째 함수는 객체가 index 슬롯을 지원하는지, 그리고 그 슬롯이 채워져 있는지 검사합니다.
- 새
operator.index(obj)함수가 추가됩니다. 이 함수는obj.__index__()와 동등한 호출을 수행하며, obj가 이 특수 메서드를 구현하지 않았으면 오류를 발생시킵니다.
구현 계획
object.h에nb_index슬롯을 추가하고typeobject.c를 수정하여__index__메서드를 생성합니다ceval.c의ISINT매크로를ISINDEX로 바꾸고, index 슬롯이 정의된 객체도 처리하도록 수정합니다._PyEval_SliceIndex함수가 index 슬롯이 정의된 객체도 처리하도록 수정합니다.- 첨자 접근에
as_mapping슬롯을 사용하면서 정수를 특별히 검사하는 모든 내장 객체(예: 리스트)가 이 슬롯도 검사하도록 수정합니다. - 정수와 long 정수에
nb_index슬롯을 추가합니다(자기 자신을 그대로 반환합니다) nb_index슬롯을 가진 모든 Python 객체로부터 정수를 반환하는PyNumber_IndexC-API를 추가합니다.operator.index(x)함수를 추가합니다.arrayobject.c와mmapmodule.c가 첨자 처리와 그 밖의 용도에 새 C-API를 사용하도록 수정합니다.- 단위 테스트를 추가합니다
논의할 질문
속도
인덱스로 사용되는 정수와 long 정수는 같은 수의 명령어로 처리를 마치므로, 이 구현이 Python의 속도를 떨어뜨리지는 않을 것입니다. 유일한 변화는 예전에 오류를 일으키던 것이 이제는 허용된다는 점입니다.
이미 있는 nb_int를 사용하지 않는 이유는 무엇입니까?
nb_int 메서드는 강제 변환에 사용되므로, 여기서 요구하는 것과는 근본적으로 다른 의미를 지닙니다. 이 PEP는 이미 정수로 간주될 수 있는 대상이 Python이 정수를 필요로 할 때 그 정보를 Python에 전달하는 방법을 제안합니다. nb_int를 사용하는 것이 좋지 않은 이유를 보여 주는 가장 큰 예는 float 객체가 이미 nb_int 메서드를 정의하고 있지만, float 객체는 시퀀스의 인덱스로 사용되어서는 안 된다는 점입니다.
이름이 __index__인 이유는 무엇입니까?
이 슬롯을 다르게 해석할 여지도 있다는 점에서 __index__라는 이름에 대해 몇 가지 의문이 제기되었습니다. 예를 들어, 이 슬롯은 Python이 내부적으로 정수를 필요로 하는 경우("mystring" * 3 같은 경우)라면 언제든 사용될 수 있습니다. 슬라이싱 문법이 이러한 슬롯을 두는 가장 큰 이유이기 때문에 Guido가 이 이름을 제안했으며, 결국 더 나은 이름은 나오지 않았습니다. “__discrete__”나 “__ordinal__”처럼 제안되었던 이름의 예는 토론 스레드 [1]을 참고하십시오.
nb_index에서 PyObject *를 반환하는 이유는 무엇입니까?
처음에는 nb_index 슬롯의 반환 타입으로 Py_ssize_t가 선택되었습니다. 하지만 그 결과 보기 흉하고 깨지기 쉬운 편법을 쓰지 않고는 오버플로 오류와 언더플로 오류를 추적하고 구별할 수 없었습니다. nb_index 슬롯은 Python 코어에서 최소 세 가지 다른 방식(정수를 얻기 위해, 슬라이스 끝점을 얻기 위해, 시퀀스 인덱스를 얻기 위해)으로 사용되므로, 이 모든 경우를 처리하려면 상당한 유연성이 필요합니다. 모든 사용 사례를 처리하는 데 필요한 유연성을 갖추는 것은 매우 중요합니다. 예를 들어, nb_index에 대해 Py_ssize_t를 반환하던 초기 구현에서, RAM이 2GB 이상인 32비트 머신에서는 s = 'x' * (2**100)은 동작하지만 len(s)는 2147483647로 잘린다는 사실이 발견되었습니다. 여러 수정안이 제안되었지만, 결국 오버플로를 올바르게 처리하려면 nb_index가 nb_int 및 nb_long 슬롯과 마찬가지로 Python 객체를 반환해야 한다고 결정되었습니다.
__index__는 왜 nb_index 메서드를 가진 임의의 객체를 반환할 수 없습니까?
그렇게 하면 검사하기 쉽지 않은 여러 방식으로 무한 재귀가 발생할 수 있습니다. 이 제약은 __nonzero__가 int 또는 bool을 반환해야 한다는 요구 사항과 비슷합니다.
참조 구현
SourceForge에 패치 1436368로 제출되었습니다.
참고 문헌
Copyright
This document is placed in the public domain.