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

Python 개선 제안 한국어 번역

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:


Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 Python에서 슬라이스 구문과 같이 정수가 명시적으로 필요한 모든 경우에 임의의 객체를 사용할 수 있도록 PyNumberMethodsnb_index 슬롯과 __index__ 특수 메서드를 추가할 것을 제안합니다. 이 슬롯의 이름은 슬라이스 구문에서 유래합니다.

근거

현재 정수와 롱 정수는 슬라이싱에서 특별한 역할을 합니다. 슬라이스 구문에서 허용되는 유일한 객체이기 때문입니다. 다시 말해, X가 시퀀스 프로토콜을 구현하는 객체라면 X[obj1:obj2]obj1obj2가 모두 정수 또는 롱 정수인 경우에만 유효합니다. obj1obj2가 시퀀스의 인덱스로 합리적으로 사용될 수 있음을 Python에 알릴 방법이 없습니다. 이는 불필요한 제한입니다.

예를 들어 NumPy에는 8, 16, 32, 64비트의 부호 없는 정수와 부호 있는 정수에 대응하는 8가지 서로 다른 정수 스칼라가 있습니다. 이러한 타입 객체는 Python이 진정한 정수를 기대하는 많은 곳에서 정수로 합리적으로 사용될 수 있지만, 호환되지 않는 메모리 레이아웃 때문에 Python 정수 타입을 상속할 수 없습니다. 객체가 정수처럼 동작할 수 있음을 Python에 알릴 방법이 있어야 합니다.

nb_int(및 __int__ 특수 메서드)를 이 용도로 사용할 수는 없습니다. 해당 메서드는 객체를 정수로 강제 변환하는 데 사용되기 때문입니다. 정수로 강제 변환할 수 있는 모든 객체를 Python이 진정한 정수를 기대하는 모든 곳에서 정수로 사용할 수 있도록 허용하는 것은 적절하지 않습니다. 예를 들어 슬라이싱에서 __int__를 사용해 객체를 정수로 변환한다면, 부동 소수점 객체가 슬라이싱에서 허용되어 x[3.2:5.8]이 오류를 발생시켜야 하는데도 발생시키지 않게 됩니다.

제안

PyNumberMethodsnb_index 슬롯과 이에 대응하는 __index__ 특수 메서드를 추가합니다. 객체는 Python 정수(즉, int 또는 long)를 반환하는 함수를 정의하여 nb_index 슬롯에 배치할 수 있습니다. 이 정수는 Python이 PySequence_GetSlice, PySequence_SetSlicePySequence_DelSlice에서와 같이 정수 하나를 필요로 할 때마다 적절한 Py_ssize_t값으로 변환할 수 있습니다.

사양

  1. nb_index 슬롯은 다음 시그니처를 갖습니다.:
    PyObject *index_func (PyObject *self)
    

    반환되는 객체는 Python의 IntType이거나 Python의 LongType이어야 합니다. 오류가 발생하면 적절한 오류를 설정한 상태로 NULL을 반환해야 합니다.

  2. __index__ 특수 메서드는 다음 시그니처를 갖습니다.:
    def __index__(self):
        return obj
    

    여기서 obj는 int 또는 long이어야 합니다.

  3. 새로운 추상 C API 함수 3개가 추가됩니다.
    1. 첫 번째 함수는 객체가 index 슬롯을 지원하는지, 그리고 해당 슬롯이 채워져 있는지 확인합니다.
      int PyIndex_Check(obj)
      

      객체가 nb_index 슬롯을 정의하면 true를 반환합니다.

    2. 두 번째 함수는 nb_index 호출을 감싸는 간단한 래퍼로, 호출을 사용할 수 없거나 int 또는 long을 반환하지 않으면 PyExc_TypeError를 발생시킵니다. PyIndex_CheckPyNumber_Index 호출 내부에서 수행되므로, 먼저 호환성을 확인하는 대신 이 함수를 직접 호출하여 오류를 처리할 수 있습니다.
      PyObject *PyNumber_Index (PyObject *obj)
      
    3. 세 번째 호출은 인덱싱이나 기타 용도로 객체에서 실제로 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이면 nb_index 슬롯이 양의 정수를 반환했는지 음의 정수를 반환했는지에 따라 반환값을 PY_SSIZE_T_MAX또는 PY_SSIZE_T_MIN으로 잘라냅니다. exc가 NULL이 아니면 Python 정수 또는 long을 Py_ssize_t로 변환할 때 발생한 PyExc_OverflowError를 대신하여 설정할 오류 객체입니다.

  4. operator.index(obj) 함수가 새로 추가되며, 이 함수는 obj.__index__()에 해당하는 호출을 수행하고 obj가 특수 메서드를 구현하지 않은 경우 오류를 발생시킵니다.

구현 계획

  1. object.hnb_index 슬롯을 추가하고 typeobject.c를 수정하여 __index__ 메서드를 생성합니다.
  2. ceval.cISINT 매크로를 ISINDEX로 변경하고, 인덱스 슬롯이 정의된 객체를 처리하도록 수정합니다.
  3. _PyEval_SliceIndex 함수를 변경하여 인덱스 슬롯이 정의된 객체를 처리하도록 합니다.
  4. 첨자 접근에 as_mapping 슬롯을 사용하고 정수에 대한 특수 검사를 수행하는 모든 내장 객체(예: 리스트)를 변경하여 해당 슬롯도 검사하도록 합니다.
  5. 정수와 long_integers에 nb_index 슬롯을 추가합니다(이들은 자신을 그대로 반환합니다).
  6. nb_index 슬롯을 가진 모든 Python 객체에서 정수를 반환하는 PyNumber_Index C-API를 추가합니다.
  7. operator.index(x) 함수를 추가합니다.
  8. arrayobject.cmmapmodule.c를 변경하여 첨자 연산 및 기타 작업에 새로운 C-API를 사용하도록 합니다.
  9. 단위 테스트를 추가합니다.

토론 질문

속도

인덱스로 사용되는 정수와 long 정수는 동일한 수의 명령어로 완료되므로, 구현으로 인해 Python이 느려져서는 안 됩니다. 유일한 변경 사항은 이전에 오류를 발생시키던 것이 이제 허용된다는 점입니다.

이미 존재하는 nb_int를 사용하지 않는 이유는 무엇입니까?

nb_int 메서드는 강제 변환에 사용되므로 여기서 요청하는 것과는 근본적으로 다른 의미를 가집니다. 이 PEP는 이미 정수로 간주할 수 있는 대상이 정수가 필요할 때 해당 정보를 Python에 전달하도록 하는 방법을 제안합니다. nb_int을 사용하는 것이 좋지 않은 이유를 보여 주는 가장 큰 예는 부동 소수점 객체가 이미 nb_int메서드를 정의하지만, 부동 소수점 객체를 시퀀스의 인덱스로 사용해서는 안 됩니다는 점입니다.

__index__라는 이름인 이유는 무엇입니까?

슬롯에 대한 다른 해석이 가능할 때 __index__라는 이름을 사용하는 것과 관련하여 몇 가지 의문이 제기되었습니다. 예를 들어 Python이 내부적으로 정수를 필요로 하는 모든 경우(예: "mystring" * 3)에 이 슬롯을 사용할 수 있습니다. 이 이름은 슬라이싱 문법이 이러한 슬롯을 두는 가장 큰 이유이고, 결국 더 나은 이름이 나오지 않았기 때문에 Guido가 제안했습니다. “__discrete__” 및 “__ordinal__”과 같이 제안된 이름의 예는 토론 스레드 [1]에서 확인할 수 있습니다.

nb_index에서 PyObject *를 반환하는 이유는 무엇입니까?

초기에는 nb_index 슬롯의 반환 타입으로 Py_ssize_t가 선택되었습니다. 하지만 이로 인해 보기 흉하고 취약한 편법 없이는 오버플로와 언더플로 오류를 추적하고 구분할 수 없게 되었습니다. nb_index 슬롯은 파이썬 코어 내에서 적어도 3가지 서로 다른 방식(정수를 얻는 것, 슬라이스 끝점을 얻는 것, 시퀀스 인덱스를 얻는 것)으로 사용되므로, 이 모든 경우를 처리하려면 상당한 유연성이 필요합니다. 모든 사용 사례를 처리하는 데 필요한 유연성을 갖추는 것이 매우 중요합니다. 예를 들어 nb_index에 대해 Py_ssize_t를 반환하는 초기 구현으로 인해, RAM이 2GB 이상인 32비트 머신에서 s = 'x' * (2**100)는 동작하지만 len(s)는 2147483647로 잘려버린다는 사실이 발견되었습니다. 여러 수정안이 제안되었지만, 결국 오버플로를 올바르게 처리하려면 nb_indexnb_intnb_long 슬롯과 마찬가지로 파이썬 객체를 반환해야 한다고 결정되었습니다.

__index__는 왜 nb_index 메서드를 가진 임의의 객체를 반환할 수 없습니까?

이는 확인하기 쉽지 않은 다양한 방식으로 무한 재귀를 허용하게 될 것입니다. 이 제약은 __nonzero__가 int 또는 bool을 반환해야 한다는 요구사항과 유사합니다.

참조 구현

SourceForge에 패치 1436368로 제출되었습니다.

참고 문헌