PEP 203 – 증강 할당
- Author:
- Thomas Wouters <thomas at python.org>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 13-Jul-2000
- Python-Version:
- 2.0
- Post-History:
- 14-Aug-2000
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
서론
이 PEP는 Python 2.0을 위한 증강 할당제안을 설명합니다. 이 PEP는 Python 2.0에 도입될 예정인 이 기능의 상태와 소유권을 추적합니다. 이 문서는 기능을 설명하고 기능을 지원하는 데 필요한 변경 사항을 개괄합니다. 이 PEP는 메일링 리스트 포럼 [1]에서 이루어진 논의를 요약하고, 적절한 경우 추가 정보를 위한 URL을 제공합니다. 이 파일의 CVS 개정 이력에는 확정적인 역사적 기록이 포함되어 있습니다.
제안된 의미론
증강 할당을 Python에 추가하는 제안된 패치는 다음과 같은 새 연산자를 도입합니다.:
+= -= *= /= %= **= <<= >>= &= ^= |=
이 연산자들은 일반적인 이항 형식과 동일한 연산자를 구현하지만, 좌변 객체가 지원하는 경우 연산을 제자리에서 수행하며 좌변은 한 번만 평가합니다.
이 연산자들은 증강 할당으로서 실제로 동작하므로, 수행하려는 이항 연산에 더해 모든 일반적인 로드 및 저장 연산도 수행합니다. 따라서 다음 표현식이 주어지면:
x += y
객체 x를 로드한 다음 y를 여기에 더하고, 그 결과 객체를 원래 위치에 다시 저장합니다. 두 인자에 대해 수행되는 정확한 동작은 x의 타입과, 경우에 따라 y의 타입에 따라 달라집니다.
Python에서 증강 할당의 기본 개념은 이항 연산의 결과를 좌측 피연산자에 저장하는 일반적인 방식을 더 쉽게 작성하는 것뿐만 아니라, 해당 좌측 피연산자가 수정된 자신의 복사본을 만드는 대신 자기 자신에 대해 동작해야 한다는 것을 알 수 있게 하는 방법이기도 합니다.
이를 가능하게 하기 위해 Python 클래스와 C 확장 타입에 여러 새로운 훅이 추가되며, 해당 객체가 증강 할당 연산의 좌변으로 사용될 때 이 훅이 호출됩니다. 클래스 또는 타입이 제자리 훅을 구현하지 않으면 해당 이항 연산에 대한 일반 훅이 사용됩니다.
따라서 인스턴스 객체 x가 주어지면 다음 표현식을 사용합니다.:
x += y
x.__iadd__(y)를 호출하려고 시도하며, 이는 __add__의 제자리 변형입니다. __iadd__가 없으면 x.__add__(y)를 시도하고, __add__도 없으면 마지막으로 y.__radd__(x)를 시도합니다. __iadd__에는 우측 피연산자 변형이 없는데, 그렇게 하려면 y가 x를 제자리에서 수정하는 방법을 알아야 하기 때문이며, 이는 아무리 좋게 말해도 안전하지 않습니다. __iadd__ 훅은 __add__와 유사하게 동작해야 하며, 연산 결과를 반환해야 합니다. 이 결과는 self일 수도 있으며 변수 x에 할당됩니다.
C 확장 타입의 경우 훅은 PyNumberMethods및 PySequenceMethods구조체의 멤버입니다. 이러한 메서드의 사용과 Python 인스턴스 객체와 C 타입의 혼합이 가능한 한 예상에서 벗어나지 않도록 몇 가지 특수한 의미론이 적용됩니다.
일반적인 x <augop> y의 경우(또는 PyNumber_InPlaceAPI 함수를 사용하는 유사한 경우), 연산되는 주된 객체는 x입니다. 이는 x와 y를 서로 협력하는 것으로 볼 수 있는 일반적인 이항 연산과 다릅니다. 이항 연산과 달리 제자리 연산에서는 피연산자를 서로 바꿀 수 없기 때문입니다. 그러나 제자리 수정이 지원되지 않으면 제자리 연산은 일반적인 이항 연산으로 대체되며, 그 결과 다음 규칙이 적용됩니다.
- 좌변 객체(
x)가 인스턴스 객체이고__coerce__메서드를 가지고 있으면,y를 인자로 하여 해당 함수를 호출합니다. 강제 변환이 성공하고 그 결과인 좌변 객체가x와 다른 객체이면, 제자리 연산으로 처리하는 것을 중단하고 강제 변환된x와y를 인자로 하여 일반 이항 연산에 적합한 함수를 호출합니다. 연산 결과는 해당 함수가 반환하는 값입니다.강제 변환 결과가
x와 다른 객체가 아니거나x가__coerce__메서드를 정의하지 않았고,x가 이 연산에 적합한__ihook__을 가지고 있으면y를 인자로 하여 해당 메서드를 호출하며, 연산 결과는 해당 메서드가 반환하는 값입니다. - 그렇지 않고 좌변 객체가 인스턴스 객체가 아니지만 해당 타입이 이 연산을 위한 제자리 함수를 정의하면,
x와y를 인자로 하여 해당 함수를 호출하며, 연산 결과는 해당 함수가 반환하는 값입니다.이 경우
x나y에 대해서는 어떠한 강제 변환도 수행되지 않으며, C 타입이 두 번째 인자로 인스턴스 객체를 받는 것은 완전히 유효합니다. 이는 일반 이항 연산에서는 일어날 수 없는 일입니다. - 그렇지 않으면 인자 강제 변환을 포함하여 일반 이항 연산과 정확히 동일하게(제자리 연산이 아닌 방식으로) 처리합니다. 간단히 말해, 어느 한 인자라도 인스턴스 객체이면
__coerce__,__hook__및__rhook__를 통해 연산을 해결합니다. 그렇지 않으면 두 객체 모두 C 타입이며, 이들을 강제 변환한 후 적절한 함수에 전달합니다. - 연산을 처리할 방법을 찾을 수 없으면 연산에 특화된 오류 메시지와 함께
TypeError를 발생시킵니다. - 시퀀스에서 특별한 의미를 가지는
+와*의 경우를 처리하기 위해 일부 특수 처리가 존재합니다.+인 시퀀스 연결의 경우, C 타입이sq_concat또는sq_inplace_concat을 정의하면 어떠한 강제 변환도 수행하지 않습니다.*인 시퀀스 반복의 경우,sq_inplace_repeat또는sq_repeat를 호출하기 전에y를 C 정수로 변환합니다.y가 인스턴스인 경우에도 이 작업을 수행하지만,x가 인스턴스인 경우에는 수행하지 않습니다.
제자리 함수는 연산이 실제로 제자리에서 수행되었다면 이전 x객체에 대한 참조를, 그렇지 않으면 새 객체에 대한 참조를 반환하는 식으로 항상 새 참조를 반환해야 합니다.
근거
Python에 이 기능을 추가하는 데에는 표현식의 단순성과 제자리 연산 지원이라는 두 가지 주요 이유가 있습니다. 최종 결과는 구문 단순성과 표현식 단순성 사이의 절충입니다. 대부분의 새로운 기능과 마찬가지로 증강 대입은 이전에는 불가능했던 어떤 것도 추가하지 않습니다. 단지 이러한 작업을 더 쉽게 수행할 수 있도록 할 뿐입니다.
증강 대입을 추가하면 Python의 구문은 더 복잡해집니다. 이제 단일 대입 연산 대신 12개의 대입 연산이 존재하며, 그중 11개는 이항 연산도 수행합니다. 그러나 이 11개의 새로운 대입 형식은 대입과 이항 연산의 결합으로 쉽게 이해할 수 있으며, 이해하는 데 큰 개념적 도약이 필요하지 않습니다. 또한 증강 대입을 지원하는 언어들은 증강 대입이 인기 있고 널리 사용되는 기능임을 보여 주었습니다. 다음 형식의 식:
<x> = <x> <operator> <y>
은 이러한 언어에서 추가 구문을 사용할 만할 정도로 충분히 흔하며, Python에도 그러한 식이 상당히 적은 것은 아닙니다. 사실은 정반대입니다. Python에서는 이항 연산자를 사용하여 리스트를 연결할 수도 있으며, 이는 매우 자주 수행되는 작업이기 때문입니다. 위 식을 다음과 같이 작성하면:
<x> <operator>= <y>
변경되는 대상이 <x>이라는 사실이 독자에게 즉시 명백하기 때문에 더 읽기 쉽고 오류가 발생할 가능성도 낮으며, <x>가 거의 비슷하지만 완전히 그렇지는 않은, <x>와는 전혀 다른 무언가로 대체되는 것이 아니라는 점도 분명합니다.
새로운 제자리 연산은 행렬 계산 및 큰 객체가 필요한 기타 애플리케이션에 특히 유용합니다. 이러한 패키지는 사용 가능한 프로그램 메모리를 효율적으로 처리하기 위해 현재의 이항 연산을 무작정 사용할 수 없습니다. 이러한 연산은 항상 새 객체를 생성하므로, 기존의 (큰) 객체에 항목 하나를 추가하면 전체 객체를 복사하고(애플리케이션의 메모리가 부족해질 수 있음), 항목 하나를 추가한 다음, 참조 횟수에 따라 원래 객체를 삭제하게 될 수 있습니다.
이 문제를 해결하려면 패키지는 현재 메서드나 함수를 사용하여 객체를 제자리에서 수정해야 하는데, 이는 증강 대입 표현식보다 확실히 읽기 어렵습니다. 증강 대입이 이러한 패키지의 모든 문제를 해결하지는 못합니다. 일부 연산은 애초에 제한된 이항 연산자 집합으로 표현할 수 없기 때문입니다. 그러나 이는 시작점입니다. PEP 211에서는 새로운 연산자를 추가하는 방안을 검토하고 있습니다.
새로운 메서드
제안된 구현은 Python 클래스가 구현하여 증강 대입 연산을 오버로드할 수 있는 다음 11개의 가능한 후크를 추가합니다.:
__iadd__
__isub__
__imul__
__idiv__
__imod__
__ipow__
__ilshift__
__irshift__
__iand__
__ixor__
__ior__
__iadd__의 i는 제자리를 뜻합니다.
C 확장 타입에는 다음 구조체 멤버가 추가됩니다.
PyNumberMethods에:
binaryfunc nb_inplace_add;
binaryfunc nb_inplace_subtract;
binaryfunc nb_inplace_multiply;
binaryfunc nb_inplace_divide;
binaryfunc nb_inplace_remainder;
binaryfunc nb_inplace_power;
binaryfunc nb_inplace_lshift;
binaryfunc nb_inplace_rshift;
binaryfunc nb_inplace_and;
binaryfunc nb_inplace_xor;
binaryfunc nb_inplace_or;
PySequenceMethods로:
binaryfunc sq_inplace_concat;
intargfunc sq_inplace_repeat;
이진 호환성을 유지하기 위해, tp_flags TypeObject 멤버는 해당 TypeObject가 이 슬롯들을 위한 공간을 할당했는지 판단하는 데 사용됩니다. 이진 호환성의 완전한 단절이 이루어지기 전까지는(이는 2.0 이전에 일어날 수도, 일어나지 않을 수도 있습니다) 새 구조체 멤버 중 하나를 사용하고자 하는 코드는 먼저 PyType_HasFeature() 매크로를 사용하여 그것들이 사용 가능한지 확인해야 합니다:
if (PyType_HasFeature(x->ob_type, Py_TPFLAGS_HAVE_INPLACE_OPS) &&
x->ob_type->tp_as_number && x->ob_type->tp_as_number->nb_inplace_add) {
/* ... */
이 확인은 메서드 슬롯에 대해 NULL 값을 검사하기 전에도 이루어져야 합니다! 이 매크로는 슬롯이 사용 가능한지만 검사할 뿐, 메서드로 채워져 있는지는 검사하지 않습니다.
구현
증강 할당(augmented assignment)의 현재 구현 [2]은 이미 다룬 메서드와 슬롯에 더해, 13개의 새 바이트코드와 13개의 새 API 함수를 추가합니다.
이 API 함수들은 단순히 현재의 이항 연산 API 함수의 인플레이스 버전입니다:
PyNumber_InPlaceAdd(PyObject *o1, PyObject *o2);
PyNumber_InPlaceSubtract(PyObject *o1, PyObject *o2);
PyNumber_InPlaceMultiply(PyObject *o1, PyObject *o2);
PyNumber_InPlaceDivide(PyObject *o1, PyObject *o2);
PyNumber_InPlaceRemainder(PyObject *o1, PyObject *o2);
PyNumber_InPlacePower(PyObject *o1, PyObject *o2);
PyNumber_InPlaceLshift(PyObject *o1, PyObject *o2);
PyNumber_InPlaceRshift(PyObject *o1, PyObject *o2);
PyNumber_InPlaceAnd(PyObject *o1, PyObject *o2);
PyNumber_InPlaceXor(PyObject *o1, PyObject *o2);
PyNumber_InPlaceOr(PyObject *o1, PyObject *o2);
PySequence_InPlaceConcat(PyObject *o1, PyObject *o2);
PySequence_InPlaceRepeat(PyObject *o, int count);
이들은 (두 객체 중 하나라도 파이썬 클래스 인스턴스인 경우) 파이썬 클래스 후크를 호출하거나, C 타입의 숫자 또는 시퀀스 메서드를 호출합니다.
새 바이트코드는 다음과 같습니다:
INPLACE_ADD
INPLACE_SUBTRACT
INPLACE_MULTIPLY
INPLACE_DIVIDE
INPLACE_REMAINDER
INPLACE_POWER
INPLACE_LEFTSHIFT
INPLACE_RIGHTSHIFT
INPLACE_AND
INPLACE_XOR
INPLACE_OR
ROT_FOUR
DUP_TOPX
INPLACE_* 바이트코드는 BINARY_* 바이트코드를 그대로 따르지만, InPlace API 함수 호출로 구현된다는 점이 다릅니다. 나머지 두 바이트코드는 유틸리티 바이트코드입니다: ROT_FOUR는 ROT_THREE처럼 동작하지만, 스택 최상단의 네 항목을 회전시킨다는 점이 다릅니다.
DUP_TOPX는 단일 인자를 받는 바이트코드로, 이 인자는 1에서 5 사이(포함)의 정수여야 하며 한 블록에서 복제할 항목의 개수를 나타냅니다. 다음과 같은 스택이 있다고 할 때(리스트의 오른쪽이 스택의 top입니다):
[1, 2, 3, 4, 5]
DUP_TOPX 3은 맨 위 3개 항목을 복제하여 다음과 같은 스택이 됩니다:
[1, 2, 3, 4, 5, 3, 4, 5]
인자가 1인 DUP_TOPX는 DUP_TOP과 동일합니다. 5라는 제한은 순전히 구현상의 제한입니다. 증강 할당의 구현에는 인자가 2와 3인 DUP_TOPX만 필요하며, 상당수의 DUP_TOP과 ROT_*를 사용하면 이 새로운 opcode 없이도 처리할 수 있습니다.
미해결 사항
PyNumber_InPlace API는 일반 PyNumber API의 부분집합일 뿐입니다: 증강 할당 문법을 지원하는 데 필요한 함수들만 포함되어 있습니다. 다른 in-place API 함수가 필요하다면 나중에 추가할 수 있습니다.
DUP_TOPX 바이트코드는 편의를 위한 바이트코드이며 실제로 필요하지는 않습니다. 이 바이트코드를 둘 가치가 있는지 검토해야 합니다. 현재로서는 이 바이트코드의 다른 용도는 없어 보입니다.
Copyright
This document has been placed in the public domain.