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 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

많은 Python 타입이 수치 연산을 구현합니다. 수치 연산의 인자들이 서로 다른 타입이면, 인터프리터는 인자들을 공통 타입으로 강제 변환하려고 시도합니다. 그런 다음 이 공통 타입을 사용하여 수치 연산을 수행합니다. 이 PEP는 타입의 수치 연산에 전달되는 인자를 강제 변환하지 않아야 함을 나타내는 새로운 타입 플래그를 제안합니다. 주어진 타입을 지원하지 않는 연산은 새로운 싱글턴 객체를 반환하여 이를 알립니다. 타입 플래그를 설정하지 않은 타입은 하위 호환성을 유지하는 방식으로 처리됩니다. 인터프리터가 강제 변환을 수행하게 하는 것보다 연산이 서로 다른 타입을 직접 처리하게 하는 편이 대체로 더 단순하고, 더 유연하며, 더 빠릅니다.

근거

수치 연산이나 이와 관련된 다른 연산을 구현할 때는, 정수 + 정수처럼 한 가지 타입의 피연산자 사이의 연산만 제공하는 것이 아니라 정수 + 부동소수점처럼 그 연산의 기본 개념을 다른 타입 조합으로도 일반화하는 것이 바람직한 경우가 많습니다.

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

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

이러한 단점들 때문에 이런 기능이 필요한 타입을 구현하는 일이 불가능하지는 않더라도 매우 번거로워지므로, 이 상황을 해결할 방안이 분명히 필요합니다. 예를 들어 DateTime과 DateTimeDelta [1] 타입을 살펴보십시오. 앞의 것은 절대값이고, 뒤의 것은 상대값입니다. 절대값에는 언제나 상대값을 더할 수 있으며, 그 결과로 새로운 절대값을 얻습니다. 하지만 기존 강제 변환 메커니즘이 그 연산을 구현하는 데 사용할 수 있는 공통 타입은 존재하지 않습니다.

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

명세

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

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

거의 100%에 가까운 하위 호환성을 유지하려면, 새로운 전략에 대해 전혀 알지 못하는 숫자(구식 숫자)도 새로운 방식을 기대하는 숫자(신식 숫자)와 똑같이 잘 동작하도록 매우 주의해야 합니다. 또한 바이너리 호환성은 필수입니다. 즉, 인터프리터는 숫자가 신식 연산을 사용할 수 있음을 나타내는 경우에만 그 연산에 접근하고 사용할 수 있습니다.

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

새 스타일 슬롯은 전달받은 인자 타입 조합을 처리할 수 없다고 판단하면, 호출자에게 특수 싱글턴 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)
기존 기존 기존 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)) 하나뿐입니다).

숫자 프로토콜은 시퀀스 연결과 같은 몇 가지 관련 작업에도 사용된다는 점에 유의하십시오. 이러한 작업도 새로운 메커니즘이 없으면 동작하지 않을 타입 조합에 대해 우측 연산을 구현함으로써 새로운 메커니즘의 이점을 누릴 수 있습니다. 문자열 연결을 예로 들어 보겠습니다. 현재는 string + string만 할 수 있습니다. 새로운 메커니즘을 사용하면, 문자열이 new_type에 대해 전혀 알지 못하더라도 문자열과 유사한 새로운 타입이 new_type + string과 string + new_type을 구현할 수 있습니다.

비교 역시 강제 변환에 의존하므로(정수를 부동소수점 수와 비교할 때마다 정수가 먼저 부동소수점 수로 변환된 다음 비교됩니다…), 숫자 비교를 처리하는 새로운 슬롯이 필요합니다:

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

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

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

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

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

이 새로운 API를 사용하도록 PyObject_Compare() API도 그에 맞게 조정해야 합니다.

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

참조 구현

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

감사의 말

이 PEP와 패치는 Marc-André Lemburg [3]의 작업에 크게 기반하고 있습니다.

References