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

Python 개선 제안 한국어 번역

PEP 208 – 강제 변환 모델 재설계

Author:
Neil Schemenauer <nas at arctrix.com>, Marc-André Lemburg <mal at lemburg.com>
Status:
Final
Type:
Standards Track
Created:
04-Dec-2000
Python-Version:
2.1
Post-History:


Table of Contents

번역·라이선스 안내

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

개요

많은 파이썬 타입이 수치 연산을 구현합니다. 수치 연산의 인자들이 서로 다른 타입일 때, 인터프리터는 그 인자들을 공통 타입으로 강제 변환하려고 시도합니다. 그런 다음 이 공통 타입을 사용하여 수치 연산이 수행됩니다. 이 PEP는 타입의 수치 연산에 대한 인자들이 강제 변환되지 않아야 함을 나타내는 새로운 타입 플래그를 제안합니다. 제공된 타입들을 지원하지 않는 연산은 새로운 싱글턴 객체를 반환함으로써 이를 나타냅니다. 타입 플래그를 설정하지 않는 타입들은 하위 호환 방식으로 처리됩니다. 연산이 서로 다른 타입을 직접 처리하도록 허용하는 것은 인터프리터가 강제 변환을 수행하는 것보다 흔히 더 단순하고, 더 유연하며, 더 빠릅니다.

근거

수치 연산이나 그와 관련된 다른 연산을 구현할 때는, 예컨대 정수 + 정수처럼 한 타입의 피연산자 사이의 연산만을 제공하는 것이 아니라, 예컨대 정수 + 부동소수점처럼 그 연산 이면의 개념을 다른 타입 조합으로도 일반화하여 제공하는 것이 흔히 바람직합니다.

이러한 혼합 타입 상황에 대한 일반적인 접근법은 피연산자들을 공통 타입으로 “끌어올리는”(강제 변환) 방법을 제공한 다음, 그 타입의 피연산자 메서드를 실행 메커니즘으로 사용하는 것입니다. 그러나 이 전략에는 몇 가지 단점이 있습니다.

  • “끌어올림” 과정이 적어도 하나의 새로운(임시) 피연산자 객체를 생성한다는 점,
  • 강제 변환 메서드가 뒤따를 연산에 대해 전달받지 못하기 때문에, 연산별로 특화된 타입의 강제 변환을 구현하는 것이 불가능하다는 점,
  • 공통 타입을 마련할 수 없는 상황을 우아하게 해결할 방법이 없다는 점, 그리고
  • 강제 변환 메서드는 항상 연산 자체의 메서드보다 먼저 호출되어야 합니다.

이러한 단점들은 이런 기능이 필요한 타입의 구현을 매우 번거롭게, 심지어 불가능하게 만들기 때문에, 이 상황에 대한 수정이 명백히 필요합니다. 예를 들어, DateTimeDateTimeDelta [1] 타입을 살펴보십시오. 전자는 절대값이고 후자는 상대값입니다. 상대값을 절대값에 항상 더할 수 있으며, 이는 새로운 절대값을 만들어냅니다. 하지만 기존의 강제 변환 메커니즘이 그 연산을 구현하는 데 사용할 수 있는 공통 타입은 없습니다.

현재 PyInstance 타입은 인터프리터에 의해 특별하게 취급되는데, 이는 그 수치 메서드들이 서로 다른 타입의 인자를 전달받기 때문입니다. 이 특별한 경우를 제거하면 인터프리터가 단순해지고, 다른 타입들이 인스턴스 타입처럼 동작하는 수치 메서드를 구현할 수 있게 됩니다. 이는 ExtensionClass과 같은 확장 타입에 특히 유용합니다.

명세

중앙 집중식 강제 변환 메서드를 사용하는 대신, 서로 다른 피연산자 타입을 처리하는 과정은 단순히 연산 자체에 맡겨집니다. 연산이 주어진 피연산자 타입 조합을 처리할 수 없다고 판단하면, 지시자로서 특별한 싱글턴을 반환할 수 있습니다.

파이썬으로 작성된 “숫자”(수치 프로토콜의 전체 또는 일부를 구현하는 모든 것)는 이미 이 전략의 첫 번째 부분을 사용하고 있다는 점에 유의하십시오. 여기서 우리가 초점을 맞추는 것은 C 수준 API입니다.

거의 100%의 하위 호환성을 유지하기 위해, 새로운 전략에 대해 전혀 알지 못하는 숫자(구식 숫자)가 새로운 체계를 기대하는 숫자(신식 숫자)와 마찬가지로 잘 동작하도록 매우 주의해야 합니다. 또한, 바이너리 호환성은 필수인데, 이는 숫자가 신식 연산의 사용 가능 여부를 표시하는 경우에만 인터프리터가 그 연산에 접근하여 사용할 수 있다는 것을 의미합니다.

새 스타일 숫자는 타입 플래그 Py_TPFLAGS_CHECKTYPES를 설정한 경우에만, 그리고 그 경우에 한해서만 인터프리터에 의해 새 스타일로 간주됩니다. 구식 숫자와 새 스타일 숫자의 주요 차이점은 숫자 슬롯 함수가 더 이상 전달되는 인자가 동일한 타입일 것이라고 가정할 수 없다는 것입니다. 새 스타일 슬롯은 모든 인자에 대해 올바른 타입을 검사하고 필요한 변환을 직접 구현해야 합니다. 이는 타입 구현자 입장에서 더 많은 작업을 야기하는 것처럼 보일 수 있지만, 실제로는 구식 강제 변환 슬롯에 대해 같은 종류의 루틴을 작성하는 것보다 더 어렵지 않습니다.

새 스타일 슬롯이 전달된 인자 타입 조합을 처리할 수 없다는 것을 발견하면, 호출자에게 특별한 싱글턴 Py_NotImplemented의 새로운 참조를 반환할 수 있습니다. 이렇게 하면 호출자는 특정 타입 조합에 대한 연산을 실제로 구현하는 슬롯을 찾을 때까지 다른 피연산자의 연산 슬롯을 시도하게 됩니다. 가능한 슬롯 중 어느 것도 성공하지 못하면 TypeError를 발생시킵니다.

구현을 이해하기 쉽게 만들기 위해(전체 주제가 이미 충분히 난해하므로), 숫자 연산 처리에 새로운 계층이 도입되었습니다. 이 계층은 구식 숫자와 새 스타일 숫자의 가능한 모든 조합을 다룰 때 고려해야 하는 모든 다양한 경우를 처리합니다. 이는 두 개의 정적 함수 binary_op()ternary_op()에 의해 구현되며, 둘 다 Objects/abstract.c의 함수들만 접근할 수 있는 내부 함수입니다. 숫자 API(PyNumber_*)는 이 새로운 계층에 쉽게 맞출 수 있습니다.

부수 효과로 모든 숫자 슬롯을 NULL 검사할 수 있게 됩니다(어차피 해야 하는 작업이므로, 추가된 기능은 별도의 비용 없이 얻어집니다).

이 계층이 이항 연산을 실행하는 데 사용하는 방식은 다음과 같습니다:

v w 취해지는 동작
신형 신형 v.op(v,w), w.op(v,w)
신형 구형 v.op(v,w), coerce(v,w), v.op(v,w)
구형 신형 w.op(v,w), coerce(v,w), v.op(v,w)
구형 구형 coerce(v,w), v.op(v,w)

지정된 동작 시퀀스는 연산이 성공하여 유효한 결과(Py_NotImplemented가 아닌 값)가 반환되거나 예외가 발생할 때까지 왼쪽에서 오른쪽으로 실행됩니다. 예외는 있는 그대로 호출한 함수로 반환됩니다. 슬롯이 Py_NotImplemented를 반환하면, 시퀀스의 다음 항목이 실행됩니다.

coerce(v,w)는 PyNumber_Coerce()호출을 통해 구식 nb_coerce 슬롯 메서드를 사용한다는 점에 유의하십시오.

삼항 연산은 처리해야 할 경우가 몇 가지 더 있습니다:

v w z 수행된 동작
v.op(v,w,z), w.op(v,w,z), z.op(v,w,z)
예전 v.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
예전 w.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
이전 이전 신규 z.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
신규 신규 이전 v.op(v,w,z), w.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
신규 이전 이전 v.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
이전 신규 이전 w.op(v,w,z), coerce(v,w,z), v.op(v,w,z)
old old old coerce(v,w,z), v.op(v,w,z)

위와 동일한 사항이지만, coerce(v,w,z)가 실제로 하는 일은 다음과 같습니다:

if z != Py_None:
    coerce(v,w), coerce(v,z), coerce(w,z)
else:
    # treat z as absent variable
    coerce(v,w)

현재 구현은 이미 이 방식을 사용하고 있습니다(삼항 슬롯은 nb_pow(a,b,c)) 하나뿐입니다).

수치 프로토콜은 시퀀스 연결(concatenation) 같은 다른 관련 작업에도 사용된다는 점에 유의하십시오. 이런 작업들도 새로운 메커니즘의 혜택을 받을 수 있는데, 그렇지 않으면 동작하지 않을 타입 조합에 대해 오른쪽 연산을 구현함으로써 가능합니다. 예를 들어 문자열 연결(concatenation)을 생각해 보십시오: 현재는 문자열 + 문자열만 가능합니다. 새로운 메커니즘을 사용하면, 문자열과 유사한 새로운 타입이 new_type + string 및 string + new_type을 구현할 수 있게 되는데, 이는 문자열이 new_type에 대해 전혀 알지 못하는 경우에도 가능합니다.

비교 연산 또한 강제 변환(coercion)에 의존하기 때문에(정수와 실수를 비교할 때마다 정수가 먼저 실수로 변환된 다음 비교됩니다…), 수치 비교를 처리할 새로운 슬롯이 필요합니다:

PyObject *nb_cmp(PyObject *v, PyObject *w)

이 슬롯은 두 객체를 비교하고 그 결과를 나타내는 정수 객체를 반환해야 합니다. 현재 이 결과 정수는 -1, 0, 1만 가능합니다. 슬롯이 해당 타입 조합을 처리할 수 없는 경우, Py_NotImplemented에 대한 참조를 반환할 수 있습니다. [XXX 이 슬롯은 리치 비교(rich comparison)(즉 PEP 207)를 고려해야 하므로 아직 유동적이라는 점에 유의하십시오.]

수치 비교는 새로운 수치 프로토콜 API로 처리됩니다:

PyObject *PyNumber_Compare(PyObject *v, PyObject *w)

이 함수는 두 객체를 “숫자”로 비교하여 그 결과를 나타내는 정수 객체를 반환합니다. 현재 이 결과 정수는 -1, 0, 1 중 하나만 될 수 있습니다. 주어진 객체들이 해당 연산을 처리할 수 없는 경우, TypeError가 발생합니다.

PyObject_Compare() API는 이 새로운 API를 활용할 수 있도록 그에 맞게 조정되어야 합니다.

다른 변경 사항으로는 일부 내장 함수(예: cmp())를 이 API를 사용하도록 적응시키는 것도 포함됩니다. 또한, PyNumber_CoerceEx()nb_coerce 슬롯을 호출하기 전에 새로운 스타일의 숫자인지 확인해야 합니다. 새로운 스타일의 숫자는 강제 변환 슬롯을 제공하지 않으므로 명시적으로 강제 변환될 수 없습니다.

참조 구현

CVS 버전의 Python에 대한 예비 패치는 Source Forge 패치 관리자 [2]를 통해 이용할 수 있습니다.

크레딧

이 PEP와 패치는 Marc-André Lemburg [3]가 수행한 작업을 크게 기반으로 하고 있습니다.

References