PEP 209 – 다차원 배열
- Author:
- Paul Barrett <barrett at stsci.edu>, Travis Oliphant <oliphant at ee.byu.edu>
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 03-Jan-2001
- Python-Version:
- 2.2
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 새로운 기능과 특성을 모듈에 더 쉽게 추가할 수 있도록, 다차원 배열 모듈인 Numeric의 재설계와 재구현을 제안합니다. Numeric 2에서 특별히 주목할 측면은 기가바이트를 초과하는 크기의, 이질적인 데이터 구조나 레코드로 구성된 배열에 대한 효율적인 접근입니다. 제안된 설계는 ArrayType, UFunc, Array, ArrayView라는 네 가지 Python 클래스와, 배열 연산을 효율적으로 처리하기 위한 저수준 C 확장 모듈 _ufunc를 사용합니다. 또한, 각 배열 타입은 해당 타입에 대한 강제 변환 규칙, 연산, 메서드를 정의하는 자체 C-확장 모듈을 가집니다. 이 설계는 새로운 타입, 기능, 특성을 모듈 방식으로 추가할 수 있게 해줍니다. 새 버전은 현재의 Numeric과 몇 가지 비호환성을 도입할 것입니다.
동기
다차원 배열은 과학, 공학, 컴퓨팅 분야에서 데이터를 저장하고 조작하는 데 흔히 사용됩니다. 파이썬에는 현재 Numeric(이하 Numeric 1이라 함)이라는 확장 모듈이 있으며, 이는 적당한 크기(10MB 규모)의 동질적인 데이터 배열을 다루는 사용자에게 만족스러운 기능들을 제공합니다. 더 큰 배열(100MB 이상 규모), 그리고 이질적일 수 있는 데이터에 대한 접근에 있어서는, Numeric 1의 구현이 비효율적이고 번거롭습니다. 앞으로 Numerical Python 커뮤니티가 추가 기능을 요청할 가능성도 높은데, 이는 PEP 211: Adding New Linear Operators to Python과 PEP 225: Elementwise/Objectwise Operators에서 잘 드러납니다.
제안
이 제안은 Numeric 1을 재설계하고 재구현할 것을 권고하며, 이후 Numeric 2로 불릴 이 버전은 새로운 타입, 기능, 그리고 함수 기능을 쉽고 모듈화된 방식으로 추가할 수 있게 해줍니다. Numeric 2의 초기 설계는 다양한 타입의 배열을 조작하기 위한 범용 프레임워크를 제공하는 데 초점을 맞추어야 하며, 새로운 배열 타입과 UFunc를 추가할 수 있는 간단한 메커니즘을 제공해야 합니다. 다양한 분야에 더 특화된 함수형 메서드는 이후 이 핵심 위에 계층으로 추가할 수 있습니다. 이 새 모듈은 여전히 Numeric으로 불릴 것이며, Numeric 1에서 볼 수 있는 동작 대부분이 그대로 유지될 것입니다.
제안된 설계는 네 가지 파이썬 클래스인 ArrayType, UFunc, Array, ArrayView와, 배열 연산을 효율적으로 처리하기 위한 저수준 C 확장 모듈을 사용합니다. 또한 각 배열 타입은 해당 타입에 대한 강제 변환 규칙, 연산, 메서드를 정의하는 자체 C 확장 모듈을 가집니다. 이후 핵심 기능이 안정화되면 일부 파이썬 클래스는 C 확장 타입으로 전환될 수 있습니다.
계획된 기능은 다음과 같습니다:
- 개선된 메모리 사용량
이 기능은 대규모 배열을 다룰 때 특히 중요하며, 성능뿐만 아니라 메모리 사용량에서도 상당한 개선을 가져올 수 있습니다. 저희는 메모리 사용량을 개선할 수 있는 몇 가지 영역을 파악했습니다:
- 로컬 강제 변환 모델을 사용합니다임시 배열을 생성하는 파이썬의 전역 강제 변환 모델을 사용하는 대신, Numeric 2는 Numeric 1과 마찬가지로 PEP 208에서 설명된 대로 강제 변환의 책임을 연산자에게 위임하는 지역 강제 변환 모델을 구현할 것입니다. 내부 버퍼를 사용함으로써, 필요한 경우 연산 시점에 각 배열(출력 배열을 포함)에 대해 강제 변환 연산을 수행할 수 있습니다. 벤치마크 [1] 결과에 따르면 성능은 기껏해야 소폭 저하될 뿐이며, 내부 버퍼가 L2 캐시 크기보다 작고 프로세서에 부하가 걸린 경우에는 오히려 향상되는 것으로 나타났습니다. 배열 강제 변환을 완전히 피하기 위해, Numeric 2에서는 혼합 타입 인자를 갖는 C 함수가 허용됩니다.
- 임시 배열 생성 회피복잡한 배열 표현식(즉, 둘 이상의 연산을 갖는 경우)에서는 각 연산이 임시 배열을 생성하며, 이 배열은 다음 연산에서 사용된 후 삭제됩니다. 더 나은 접근 방식은 이러한 임시 배열을 식별하여, 가능한 경우—즉 생성 중인 임시 배열과 배열 형태 및 타입이 같은 경우—그 데이터 버퍼를 재사용하는 것입니다. 이는 임시 배열의 참조 카운트를 확인함으로써 수행할 수 있습니다. 참조 카운트가 1이면, 연산이 끝나는 즉시 삭제되므로 재사용 후보가 됩니다.
- 메모리 매핑 파일의 선택적 사용Numeric 사용자는 때때로 매우 큰 파일의 데이터에 접근하거나, 가용 메모리보다 큰 데이터를 처리해야 할 필요가 있습니다. 메모리 매핑 배열은 데이터를 디스크에 저장하면서도 메모리에 있는 것처럼 보이게 함으로써 이를 가능하게 하는 메커니즘을 제공합니다. 메모리 매핑 배열은 파일 접근 시 두 단계의 복사 과정 중 하나를 제거함으로써 모든 파일에 대한 접근을 향상시킬 것입니다. Numeric은 메모리 내 배열과 메모리 매핑 배열에 투명하게 접근할 수 있어야 합니다.
- 레코드 접근일부 과학 분야에서는 데이터가 파일에 이진 레코드로 저장됩니다. 예를 들어, 천문학에서는 광자 데이터가 도착 시간 순서로 1차원 광자 목록으로 저장됩니다. 이러한 레코드 또는 C와 유사한 구조체에는 도착 시간, 검출기 상의 위치, 에너지 등 검출된 광자에 대한 정보가 담겨 있습니다. 각 필드는 char, int, float와 같이 서로 다른 타입일 수 있습니다. 이러한 배열은 새로운 문제를 야기하는데, 특히 숫자 값에 올바르게 접근하려면 바이트 정렬이나 바이트 교환을 수행해야 할 수도 있습니다(다만 바이트 교환은 메모리 매핑된 데이터에서도 문제가 됩니다). Numeric 2는 데이터에 접근하거나 데이터를 조작할 때 정렬 및 표현 방식 문제를 자동으로 처리하도록 설계되었습니다. 레코드를 구현하는 데는 두 가지 접근 방식이 있는데, 관점에 따라 파생 배열 클래스로 구현하거나 특수 배열 타입으로 구현하는 방식입니다. 이 논의는 미해결 문제(Open Issues) 절로 미루겠습니다.
- 로컬 강제 변환 모델을 사용합니다
- 추가 배열 타입
Numeric 1에는 char, ubyte, sbyte, short, int, long, float, double, cfloat, cdouble, object의 11가지 정의된 타입이 있습니다. ushort, uint, ulong 타입은 없으며, 일부 과학 분야에서 유용하고 마스크 배열(masked-array) 구현에도 사용될 수 있는 비트 타입 같은 더 복잡한 타입도 없습니다. Numeric 1의 설계로 인해 이러한 타입이나 다른 타입을 추가하는 작업은 어렵고 오류가 발생하기 쉬운 과정이 됩니다. 아래에서 설명하는 비트 타입과 같은 새로운 배열 타입을 쉽게 추가(및 삭제)할 수 있게 하려면 Numeric의 재설계가 필요합니다.
- 비트 타입배열 간의 리치 비교(rich comparison) 결과는 불리언 값의 배열입니다. 이 결과는 char 타입의 배열에 저장할 수 있지만, 이는 불필요한 메모리 낭비입니다. 더 나은 구현이라면 비트나 불리언 타입을 사용하여 배열 크기를 8분의 1로 압축할 것입니다. 이는 현재 Numeric 1을 대상으로 (Travis Oliphant가) 구현 중이며, Numeric 2에도 포함되어야 합니다.
- 비트 타입
- 향상된 배열 인덱싱 구문
확장 슬라이싱 구문은 Numeric 배열을 다룰 때 1보다 큰 스텝 크기(step-size)를 허용함으로써 더 큰 유연성을 제공하기 위해 파이썬에 추가되었습니다. 이 구문은 일정한 간격으로 떨어진 인덱스 목록에 대한 축약형으로 잘 작동합니다. 불규칙한 간격으로 떨어진 인덱스 목록이 필요한 상황에서는, 향상된 배열 인덱싱 구문이 1차원 배열을 인자로 사용할 수 있게 해줄 것입니다.
- 리치 비교(rich comparison)
파이썬 2.1의 PEP 207: 리치 비교(Rich Comparisons) 구현은 배열을 다룰 때 추가적인 유연성을 제공합니다. 저희는 이 기능을 Numeric 2에 구현할 계획입니다.
- 배열 브로드캐스팅(broadcasting) 규칙
스칼라와 배열 간의 연산이 수행될 때, 암묵적인 동작은 스칼라 값을 포함하는 배열 피연산자와 동일한 형태를 가지는 새 배열을 생성하는 것입니다. 이를 배열 브로드캐스팅이라고 합니다. 이는 벡터와 같이 랭크가 더 낮은 배열에서도 동작합니다. 이 암묵적인 동작은 Numeric 1에서 구현되어 있으며 Numeric 2에서도 구현될 예정입니다.
설계 및 구현
Numeric 2의 설계는 네 가지 주요 클래스를 가지고 있습니다:
- ArrayType:
이는 배열 타입의 근본적인 속성들, 예를 들어 이름, 바이트 단위 크기, 다른 타입에 대한 강제 변환 관계 등을 기술하는 단순한 클래스입니다. 예:
Int32 = ArrayType('Int32', 4, 'doc-string')
다른 타입들과의 관계는 해당 타입의 C 확장 모듈이 임포트될 때 정의됩니다. 이에 대응하는 Python 코드는 다음과 같습니다:
Int32.astype[Real64] = Real64
이는 Real64 배열 타입이 Int32 배열 타입보다 더 높은 우선순위를 가짐을 나타냅니다.
다음의 속성과 메서드가 핵심 구현을 위해 제안됩니다. 추가 속성은 개별적으로 추가할 수 있습니다. 예를 들어 bit 타입의 경우 .bitsize나 .bitstrides와 같은 것입니다.
속성:
.name: e.g. "Int32", "Float64", etc. .typecode: e.g. 'i', 'f', etc. (for backward compatibility) .size (in bytes): e.g. 4, 8, etc. .array_rules (mapping): rules between array types .pyobj_rules (mapping): rules between array and python types .doc: documentation string
메서드:
__init__(): initialization __del__(): destruction __repr__(): representation
C-API: 아직 구체화가 필요합니다.
- UFunc:
이 클래스는 Numeric 2의 핵심입니다. 그 설계는 UFunc가 이름, 인자의 총 개수 및 입력 개수, 문서 문자열, 빈 CFunc 딕셔너리를 속성으로 갖는 싱글턴 호출 가능 객체를 생성한다는 점에서 ArrayType의 설계와 유사합니다. 예를 들면:
add = UFunc('add', 3, 2, 'doc-string')
정의된 시점에서 add 인스턴스는 연관된 C 함수가 없으므로 아무런 작업도 수행할 수 없습니다. CFunc 딕셔너리는 배열 타입용 C 확장 모듈이 임포트될 때 나중에 채워지거나 등록됩니다. register 메서드의 인자는 함수 이름, 함수 디스크립터, CUFunc 객체입니다. 이에 대응하는 Python 코드는 다음과 같습니다:
add.register('add', (Int32, Int32, Int32), cfunc-add)
Int32와 같은 배열 타입 모듈의 초기화 함수에는 두 개의 C API 함수가 있는데, 하나는 강제 변환 규칙을 초기화하는 함수이고 다른 하나는 CFunc 객체를 등록하는 함수입니다.
몇몇 배열에 연산이 적용되면
__call__메서드가 호출됩니다. 이 메서드는 각 배열의 타입을 가져오고(출력 배열이 주어지지 않은 경우, 강제 변환 규칙으로부터 생성됩니다) 인자 타입과 일치하는 키가 있는지 CFunc 딕셔너리를 확인합니다. 해당 키가 존재하면 연산이 즉시 수행되고, 그렇지 않으면 강제 변환 규칙을 사용하여 관련 연산과 변환 함수 집합을 검색합니다. 그런 다음__call__메서드는 각 배열의 슬라이스를 순회하기 위해 C로 작성된 compute 메서드를 호출하는데, 그 이름은 다음과 같습니다::_ufunc.compute(slice, data, func, swap, conv)
‘func’ 인자는 CFuncObject이고, ‘swap’과 ‘conv’ 인자는 전처리 또는 후처리가 필요한 배열들을 위한 CFuncObject의 리스트이며, 그렇지 않은 경우에는 None이 사용됩니다. data 인자는 버퍼 객체의 리스트이고, slice 인자는 각 배열과 각 차원에 대한 버퍼 오프셋 및 스텝 크기와 함께 각 차원의 반복 횟수를 제공합니다.
저희는
__call__메서드에서 사용할 몇 가지 UFunc를 미리 정의해 두었습니다: cast, swap, getobj, setobj입니다. cast와 swap 함수는 각각 강제 변환과 바이트 스왑을 수행하며, getobj와 setobj 함수는 Numeric 배열과 Python 시퀀스 간의 강제 변환을 수행합니다.다음 속성과 메서드가 핵심 구현을 위해 제안됩니다.
속성:
.name: e.g. "add", "subtract", etc. .nargs: number of total arguments .iargs: number of input arguments .cfuncs (mapping): the set C functions .doc: documentation string
메서드:
__init__(): initialization __del__(): destruction __repr__(): representation __call__(): look-up and dispatch method initrule(): initialize coercion rule uninitrule(): uninitialize coercion rule register(): register a CUFunc unregister(): unregister a CUFunc
C-API: 이 부분은 아직 구체화가 필요합니다.
- Array:
이 클래스는 배열의 모양, 타입, 데이터의 엔디언 등에 관한 정보를 담고 있습니다. ‘+’, ‘-’ 등의 연산자는 예를 들어 해당하는 UFunc 함수를 호출할 뿐입니다.
def __add__(self, other): return ufunc.add(self, other)
다음 속성, 메서드, 함수가 핵심 구현을 위해 제안됩니다.
속성:
.shape: shape of the array .format: type of the array .real (only complex): real part of a complex array .imag (only complex): imaginary part of a complex array
메서드:
__init__(): initialization __del__(): destruction __repr_(): representation __str__(): pretty representation __cmp__(): rich comparison __len__(): __getitem__(): __setitem__(): __getslice__(): __setslice__(): numeric methods: copy(): copy of array aslist(): create list from array asstring(): create string from array
함수:
fromlist(): create array from sequence fromstring(): create array from string array(): create array with shape and value concat(): concatenate two arrays resize(): resize array
C-API: 이 부분은 아직 구체화가 필요합니다.
- ArrayView
이 클래스는 Array 클래스와 유사하지만, reshape와 flat 메서드는 예외를 발생시킨다는 점이 다릅니다. 비연속(non-contiguous) 배열은 포인터와 스텝 크기 정보만으로는 reshape하거나 flatten할 수 없기 때문입니다.
C-API: 이 부분은 아직 구체화가 필요합니다.
- C-확장 모듈:
Numeric2는 여러 C-확장 모듈을 갖게 됩니다.
- _ufunc:
이 집합의 주요 모듈은 _ufuncmodule.c입니다. 이 모듈의 의도는 최소한의 작업, 즉 지정된 C 함수를 사용하여 배열을 순회하는 것만 수행하는 것입니다. 이 함수들의 인터페이스는 Numeric 1과 동일하며, 즉
int (*CFunc)(char *data, int *steps, int repeat, void *func);
그리고 그 기능도 동일할 것으로 예상되는데, 즉 가장 안쪽 차원에 대해 순회합니다.
핵심 구현을 위해 다음 속성과 메서드가 제안됩니다.
속성:
메서드:
compute():
C-API: 아직 구체화가 필요합니다.
- _int32, _real64 등:
각 배열 타입에 대해서도 C-확장 모듈이 있게 되는데, 예를 들어 _int32module.c, _real64module.c 등입니다. 앞서 언급했듯이, 이 모듈들이 UFunc 모듈에 의해 임포트될 때 자신들의 함수와 강제 변환 규칙을 자동으로 등록하게 됩니다. 이 모듈들의 새 버전이나 개선된 버전은 Numeric 2의 나머지 부분에 영향을 주지 않고 쉽게 구현하여 사용할 수 있습니다.
- _ufunc:
미해결 사안
- 슬라이싱 구문은 기본적으로 복사 동작을 하는 것입니까, 뷰 동작을 하는 것입니까?
Python의 기본 동작은 슬라이싱 구문을 사용할 때 하위 리스트나 튜플의 복사본을 반환하는 것인 반면, Numeric 1은 배열에 대한 뷰를 반환합니다. Numeric 1에서 이루어진 선택은 명백히 성능상의 이유 때문인데, 개발자들은 각 배열 연산마다 데이터 버퍼를 할당하고 복사하는 비용을 피하고자 했으며 배열의 깊은 복사가 필요한 경우는 드물다고 여겼습니다. 그러나 일부는 Numeric의 슬라이스 표기법도 Python 리스트와 일관성을 유지하기 위해 복사 동작을 가져야 한다고 주장해 왔습니다. 이 경우 복사 동작과 관련된 성능 저하는 쓰기 시 복사(copy-on-write)를 구현함으로써 최소화할 수 있습니다. 이 방식에서는 두 배열이 하나의 데이터 버퍼를 공유하다가(뷰 동작에서처럼) 둘 중 하나의 배열에 새 데이터가 할당되는 시점에 데이터 버퍼의 복사본이 만들어집니다. 그러면 뷰 동작은 ArrayView 클래스에 의해 구현될 것이며, 그 동작은 Numeric 1 배열과 유사할 것입니다. 즉, .shape은 비연속(non-contiguous) 배열에 대해서는 설정할 수 없습니다. ArrayView 클래스를 사용하면 배열이 어떤 유형의 데이터를 담고 있는지도 명시적으로 드러납니다.
- 항목 구문은 기본적으로 복사 동작과 뷰 동작 중 어느 쪽을 따릅니까?
항목 구문에서도 이와 비슷한 의문이 생깁니다. 예를 들어
a = [[0,1,2], [3,4,5]]이고b = a[0]일 때,b[0]을 변경하면a[0][0]도 변경되는데, 이는a[0]이 a의 첫 번째 행에 대한 참조 또는 뷰이기 때문입니다. 따라서 c가 2차원 배열이라면, 일관성을 위해c[i]는 c의 복사본이 아니라 c에 대한 뷰인 1차원 배열을 반환해야 할 것으로 보입니다. 그러나c[i]는 그저c[i,:]의 축약형으로 볼 수 있는데, 이는 슬라이싱 구문이 복사본을 반환한다고 가정할 경우 복사 동작을 암시합니다. Numeric 2도 리스트와 마찬가지로 동작하여 뷰를 반환해야 합니까, 아니면 복사본을 반환해야 합니까? - 스칼라 강제 변환(coercion)은 어떻게 구현됩니까?
Python은 Numeric보다 숫자 타입이 적어서 강제 변환(coercion) 문제를 일으킬 수 있습니다. 예를 들어, float 타입의 Python 스칼라와 float 타입의 Numeric 배열을 곱할 때, Python의 float 타입이 실제로는 double이기 때문에 Numeric 배열이 double로 변환됩니다. 이는 대개 원하는 동작이 아닌데, 특히 매우 큰 배열의 경우 Numeric 배열의 크기가 두 배가 되는 것이 성가실 수 있기 때문입니다. 우리는 동일한 타입 클래스, 즉 정수(integer), 부동소수점(float), 복소수(complex)에 대해서는 배열 타입이 Python 타입보다 우선하는 것을 선호합니다. 따라서 Python 정수와 Int16(short) 배열 간의 연산은 Int16 배열을 반환합니다. 반면, Python float와 Int16 배열 간의 연산은 Float64(double) 배열을 반환하게 됩니다. 두 배열 간의 연산은 일반적인 강제 변환 규칙을 사용합니다.
- 정수 나눗셈은 어떻게 처리됩니까?
Python의 미래 버전에서는 정수 나눗셈의 동작이 변경될 예정입니다. 피연산자가 float로 변환되므로 결과는 float가 됩니다. 배열이 Python 스칼라보다 우선하는 제안된 스칼라 강제 변환 규칙을 구현한다면, 배열을 정수로 나누는 연산은 정수 배열을 반환하게 되어, double 타입의 배열을 반환하는 미래 버전의 Python과 일치하지 않게 됩니다. 과학 프로그래머들은 정수 나눗셈과 부동소수점 나눗셈의 차이에 익숙하므로, Numeric 2는 이 동작을 계속 유지해야 합니까?
- 레코드는 어떻게 구현해야 합니까?
레코드를 구현하는 접근 방식에는 관점에 따라 두 가지가 있습니다. 첫 번째는 배열을 그 타입의 동작에 따라 별도의 클래스로 나누는 방식입니다. 예를 들어, 각 클래스의 연산 범위와 종류가 서로 다르기 때문에 숫자 배열이 하나의 클래스이고, 문자열이 두 번째, 레코드가 세 번째가 됩니다. 이와 같이 레코드 배열은 새로운 타입이 아니라, 더 유연한 형태의 배열을 위한 메커니즘입니다. 이러한 복잡한 데이터를 쉽게 접근하고 조작할 수 있도록, 이 클래스는 데이터 버퍼 내에서 서로 다른 바이트 오프셋을 갖는 숫자 배열들로 구성됩니다. 예를 들어, Int16, Real32 값의 배열로 구성된 테이블을 가질 수 있습니다. 오프셋이 0바이트이고 보폭(stride)이 6바이트로 Int16으로 해석되는 숫자 배열 하나와, 오프셋이 2바이트이고 보폭이 6바이트로 Real32로 해석되는 숫자 배열 하나, 이렇게 두 개의 숫자 배열이 해당 레코드 배열을 나타내게 됩니다. 두 숫자 배열 모두 동일한 데이터 버퍼를 참조하지만, 서로 다른 오프셋과 보폭 속성, 그리고 서로 다른 숫자 타입을 가집니다.
두 번째 접근 방식은 레코드를 여러 배열 타입 중 하나로 간주하는 것으로, 다만 숫자 배열에 비해 더 적고 어쩌면 다른 배열 연산을 갖게 됩니다. 이 접근 방식은 배열 타입을 고정 길이 문자열의 매핑으로 간주합니다. 이 매핑은 정수나 부동소수점 숫자처럼 단순할 수도 있고, 복소수, 바이트 문자열, C 구조체처럼 복잡할 수도 있습니다. 레코드 타입은 사실상 struct 모듈과 Numeric 모듈을 다차원 struct 배열로 병합합니다. 이 접근 방식은 배열 인터페이스에 특정한 변경을 수반합니다. 예를 들어, ‘typecode’ 키워드 인자는 아마도 더 설명적인 ‘format’ 키워드로 변경되어야 할 것입니다.
- 레코드 시맨틱스는 어떻게 정의되고 구현됩니까?레코드에 어떤 구현 방식을 취하든, 레코드의 하위 필드에 접근하고자 한다면 그것들에 접근하고 조작하는 방법의 구문과 의미를 결정해야 합니다. 이 경우, 레코드 타입은 본질적으로 struct 모듈의 unpack 메서드가 반환하는 튜플과 같이 비균질 리스트로 간주될 수 있으며, 1차원 레코드 배열은 두 번째 차원이 필드 리스트에 대한 인덱스인 2차원 배열로 해석될 수 있습니다. 이러한 향상된 배열 의미론은 하나 이상의 필드로 이루어진 배열에 대한 접근을 쉽고 간단하게 만들어 줍니다. 또한 사용자가 필드에 대해 자연스럽고 직관적인 방식으로 배열 연산을 수행할 수 있게 해줍니다. 레코드가 배열 타입으로 구현된다고 가정하면, 마지막 차원은 기본값이 0이 되므로 numeric과 같은 단순 타입으로 구성된 배열에서는 무시할 수 있습니다.
- 레코드 시맨틱스는 어떻게 정의되고 구현됩니까?
- 마스크 배열은 어떻게 구현됩니까?
Numeric 1에서 마스크 배열은 별도의 배열 클래스로 구현됩니다. Numeric 2에 새로운 배열 타입을 추가할 수 있는 기능이 생김에 따라, Numeric 2의 마스크 배열은 배열 클래스 대신 새로운 배열 타입으로 구현될 수도 있습니다.
- 수치 오류(특히 IEEE 부동 소수점 오류)는 어떻게 처리됩니까?
제안자(Paul Barrett와 Travis Oliphant)들에게도 오류를 처리하는 최선 또는 선호되는 방법이 무엇인지는 명확하지 않습니다. 연산을 수행하는 대부분의 C 함수는 배열의 가장 안쪽(마지막) 차원에 대해 반복하기 때문입니다. 이 차원에는 0으로 나누기, 언더플로, 오버플로와 같이 서로 다른 유형의 오류를 하나 이상 가진 항목이 천 개 이상 포함될 수 있습니다. 또한 이러한 오류를 추적하는 것은 성능을 희생시킬 수 있습니다. 따라서 몇 가지 선택지를 제안합니다:
- 가장 심각한 오류의 메시지를 출력하고, 나머지는
- 사용자가 오류를 찾도록 하는 것입니다.
- 발생한 모든 오류에 대한 메시지와 그 발생 횟수를 출력하여
- 사용자가 오류를 찾도록 하는 것입니다.
- 발생한 모든 오류에 대한 메시지와 그 발생 위치의
- 목록을 출력하는 것입니다.
- 또는 혼합 방식을 사용하여 가장 심각한 오류만 출력하되,
- 어떤 오류가 어디에서 발생했는지는 계속 추적하는 것입니다. 이렇게 하면 오류 메시지를 간결하게 유지하면서도 사용자가 오류를 찾을 수 있습니다.
- FORTRAN 라이브러리 및 코드의 통합을 쉽게 하려면 어떤 기능이 필요합니까?
이 단계에서 Numeric 2에서 FORTRAN 라이브러리와 사용자 코드의 통합을 쉽게 할 방법을 고려하는 것이 좋습니다.
구현 단계
- 기본 UFunc 기능 구현
- 최소한의 Array 클래스:필요한 클래스 속성 및 메서드, 예: .shape, .data, .type 등.
- 최소한의 ArrayType 클래스:Int32, Real64, Complex64, Char, Object
- 최소한의 UFunc 클래스:UFunc 인스턴스화, CFunction 등록, 정렬(alignment)·바이트 스와핑·강제 변환(coercion) 규칙을 포함한 1차원 배열용 UFunc 호출.
- 최소한의 C 확장 모듈:C로 최내부 배열 루프를 수행하는 _UFunc.
이 단계는 a, b, c가 1차원 배열일 때 ‘c = add(a, b)’를 수행하는 데 필요한 것을 구현합니다. 이를 통해 새로운 UFunc를 추가하는 방법, 배열을 강제 변환하는 방법, 필요한 정보를 C 이터레이터 메서드에 전달하는 방법, 그리고 실제 계산을 수행하는 방법을 배우게 됩니다.
- 최소한의 Array 클래스:
- UFunc 이터레이터와 Array 클래스를 계속 개선합니다.
- Array 클래스를 위한 몇 가지 접근 메서드를 구현합니다:
- print, repr, getitem, setitem 등입니다.
- 다차원 배열을 구현합니다
- UFunc를 사용하여 기본 Array 메서드 일부를 구현합니다:
- +, -, *, / 등입니다.
- UFunc가 Python 시퀀스를 사용할 수 있도록 합니다.
- 표준 UFunc와 Array 클래스 동작을 완성합니다
- getslice와 setslice 동작을 구현합니다
- Array 브로드캐스팅 규칙 작업을 진행합니다
- Record 타입을 구현합니다.
- 추가 기능을 더합니다
- UFunc를 더 추가합니다
- 버퍼 또는 mmap 접근을 구현합니다
비호환성
다음은 Numeric 1과 Numeric 2 사이의 동작상 비호환성 목록입니다.
- 스칼라 강제 변환 규칙
Numeric 1은 배열과 파이썬 숫자 타입에 대해 단일한 강제 변환 규칙 집합을 가지고 있습니다. 이는 배열 표현식을 계산하는 동안 예상치 못한 성가신 문제를 일으킬 수 있습니다. Numeric 2는 배열과 파이썬 숫자 타입을 위한 규칙, 그리고 배열만을 위한 규칙, 이렇게 두 가지 강제 변환 규칙 집합을 둠으로써 이러한 문제를 극복하고자 합니다.
- savespace 속성 없음
Numeric 1의 savespace 속성은 이 속성이 설정된 배열이 설정되지 않은 배열보다 우선순위를 갖도록 합니다. Numeric 2에는 이러한 속성이 없으므로 일반적인 배열 강제 변환 규칙이 적용됩니다.
- 슬라이싱 구문은 사본을 반환합니다
Numeric 1의 슬라이싱 구문은 원본 배열에 대한 뷰를 반환합니다. Numeric 2의 슬라이싱 동작은 사본이 될 것입니다. 배열에 대한 뷰를 얻으려면 ArrayView 클래스를 사용해야 합니다.
- 불리언 비교는 불리언 배열을 반환합니다
Python의 현재 제약으로 인해 Numeric 1에서 배열 간의 비교는 불리언 스칼라를 결과로 산출합니다. Python 2.1에서 리치 비교(Rich Comparisons)가 등장하면 불리언의 배열을 반환할 수 있게 될 것입니다.
- 타입 문자는 폐지 예정입니다
Numeric 2에는 Type 인스턴스로 구성된 ArrayType 클래스가 있을 것이며, 예를 들어 부호 있는 정수를 위한 Int8, Int16, Int32, Int 등이 있습니다. Numeric 1의 타입코드 방식은 하위 호환성을 위해 계속 사용할 수 있지만, 폐지 예정입니다.
부록
- 암시적 하위 배열 순회컴퓨터 애니메이션은 동일한 형태를 가진 다수의 2차원 이미지, 즉 프레임으로 구성됩니다. 이러한 이미지들을 하나의 메모리 블록에 쌓음으로써 3차원 배열이 생성됩니다. 그러나 수행되어야 할 연산은 전체 3차원 배열을 대상으로 하는 것이 아니라, 2차원 하위 배열의 집합을 대상으로 합니다. 대부분의 배열 언어에서는 각 프레임을 추출하여 연산을 수행한 다음, for와 유사한 반복문을 사용하여 출력 배열에 다시 삽입해야 합니다. J 언어는 프레임과 배열에 대한 랭크(rank)를 지정함으로써 프로그래머가 이러한 연산을 암묵적으로 수행할 수 있게 합니다. 기본적으로 이 랭크들은 배열 생성 시 서로 동일합니다. Numeric 1은 J 언어를 기반으로 하기 때문에, Numeric 1 개발자들의 의도는 이 기능을 구현하는 것이었습니다. Numeric 1 코드는 이 동작을 구현하는 데 필요한 변수를 가지고 있지만, 실제로 구현된 적은 없습니다. Numeric 1에서 발견되는 배열 브로드캐스팅 규칙이 이 동작을 완전히 지원하지 못한다면, 저희는 Numeric 2에서 암묵적인 하위 배열 반복을 구현할 계획입니다.
Copyright
This document is placed in the public domain.