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에서 특히 중점을 두는 부분은 크기가 1기가바이트를 넘고 불균질한 자료 구조나 레코드로 구성된 배열에 효율적으로 접근하는 것입니다. 제안하는 설계는 네 개의 Python 클래스인 ArrayType, UFunc, Array, ArrayView와 배열 연산을 효율적으로 처리하는 저수준 C 확장 모듈인 _ufunc를 사용합니다. 또한 각 배열 타입에는 해당 타입의 강제 변환 규칙, 연산, 메서드를 정의하는 자체 C 확장 모듈이 있습니다. 이 설계를 통해 새로운 타입, 특성, 기능을 모듈 방식으로 추가할 수 있습니다. 새 버전에서는 현재의 Numeric과 호환되지 않는 부분이 일부 생깁니다.
동기
다차원 배열은 과학, 공학, 컴퓨팅 분야에서 데이터를 저장하고 조작하는 데 흔히 사용됩니다. 현재 Python에는 Numeric(이하 Numeric 1)이라는 확장 모듈이 있으며, 이 모듈은 중간 크기(10 MB 정도)의 동질적인 데이터 배열을 다루는 사용자에게 충분한 기능을 제공합니다. 이질적인 데이터로 이루어질 수 있는 더 큰 배열(100 MB 이상 정도)에 접근할 때는 Numeric 1의 구현이 비효율적이고 번거롭습니다. 향후 PEP 211: Python에 새로운 선형 연산자 추가(Adding New Linear Operators to Python)와 PEP 225: 요소별/객체별 연산자(Elementwise/Objectwise Operators)에서 볼 수 있듯이, Numerical Python 커뮤니티에서 추가 기능을 요청할 가능성도 높습니다.
제안
이 제안은 Numeric 1을 재설계하고 재구현할 것을 권고합니다. 이를 이하 Numeric 2라고 부르며, Numeric 2에서는 새로운 타입, 특성, 기능을 쉽게 모듈 방식으로 추가할 수 있습니다. Numeric 2의 초기 설계는 여러 타입의 배열을 조작하기 위한 범용 프레임워크를 제공하는 데 집중해야 하며, 새로운 배열 타입과 UFunc를 추가하는 간단한 메커니즘을 제공해야 합니다. 그런 다음 각 분야에 더 특화된 함수형 메서드를 이 핵심 위에 계층으로 쌓을 수 있습니다. 이 새 모듈은 여전히 Numeric이라고 불리며, Numeric 1의 동작 대부분이 유지됩니다.
제안하는 설계는 네 개의 Python 클래스인 ArrayType, UFunc, Array, ArrayView와 배열 연산을 효율적으로 처리하는 저수준 C 확장 모듈을 사용합니다. 또한 각 배열 타입에는 해당 타입의 강제 변환 규칙, 연산, 메서드를 정의하는 자체 C 확장 모듈이 있습니다. 나중에 핵심 기능이 안정되면 일부 Python 클래스를 C 확장 타입으로 변환할 수 있습니다.
계획된 기능 중 일부는 다음과 같습니다.
- 메모리 사용량 개선
이 기능은 큰 배열을 다룰 때 특히 중요하며, 메모리 사용량뿐 아니라 성능도 크게 개선할 수 있습니다. 메모리 사용량을 개선할 수 있는 몇 가지 영역을 찾았습니다.
- 지역 강제 변환 모델 사용
Numeric 2는 임시 배열을 만드는 Python의 전역 강제 변환 모델을 사용하는 대신, 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로 줄일 수 있으므로 더 나은 구현이 될 것입니다. 이 기능은 현재 (Travis Oliphant가) Numeric 1에 구현하고 있으며, Numeric 2에도 포함되어야 합니다.
- 비트 타입
- 향상된 배열 인덱싱 문법
확장 슬라이싱 문법은 1보다 큰 스텝 크기를 허용하여 Numeric 배열을 다룰 때 더 큰 유연성을 제공하기 위해 Python에 추가되었습니다. 이 문법은 일정한 간격으로 떨어진 인덱스 목록을 간략하게 표현하는 방법으로 잘 작동합니다. 불규칙한 간격으로 떨어진 인덱스 목록이 필요한 상황에서는, 향상된 배열 인덱싱 문법을 통해 1차원 배열을 인자로 사용할 수 있을 것입니다.
- 풍부한 비교
Python 2.1의 PEP 207: Rich Comparisons 구현은 배열을 다룰 때 추가적인 유연성을 제공합니다. 저희는 이 기능을 Numeric 2에 구현할 계획입니다.
- 배열 브로드캐스팅 규칙
스칼라와 배열 사이에 연산을 수행하면, 배열 피연산자와 같은 형태를 가지며 스칼라 값으로 채워진 새 배열을 만드는 동작이 암묵적으로 일어납니다. 이를 배열 브로드캐스팅이라고 합니다. 이 동작은 벡터처럼 랭크가 더 낮은 배열에도 적용됩니다. 이 암묵적 동작은 Numeric 1에 구현되어 있으며 Numeric 2에도 구현될 것입니다.
설계와 구현
Numeric 2의 설계에는 네 가지 주요 클래스가 있습니다:
- ArrayType:
이 클래스는 배열 타입의 기본 속성, 예를 들어 이름, 바이트 단위 크기, 다른 타입에 대한 강제 변환(coercion) 관계 등을 기술하는 간단한 클래스입니다. 예:
Int32 = ArrayType('Int32', 4, 'doc-string')
다른 타입과의 관계는 해당 타입의 C 확장 모듈을 임포트할 때 정의됩니다. 이에 해당하는 Python 코드는 다음과 같습니다.:
Int32.astype[Real64] = Real64
이는 Real64 배열 타입이 Int32 배열 타입보다 우선순위가 높다는 뜻입니다.
핵심 구현을 위해 다음 속성과 메서드를 제안합니다. 추가 속성은 개별적으로 추가할 수 있습니다. 예를 들어 비트 타입에는 .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__메서드에서 사용할 수 있도록 cast, swap, getobj, setobj라는 몇 가지 UFunc를 미리 정의해 두었습니다. 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 메서드는 예외를 발생시킵니다. 연속적이지 않은 배열은 포인터와 스텝 크기 정보만으로 모양을 바꾸거나 평탄화할 수 없기 때문입니다.
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를 설정할 수 없습니다. 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는 리스트와 같은 방식으로 동작하여 뷰를 반환해야 합니까, 아니면 복사본을 반환해야 합니까? - 스칼라 강제 변환은 어떻게 구현됩니까?
Python은 Numeric보다 숫자 타입이 적어서 강제 변환(coercion) 문제가 생길 수 있습니다. 예를 들어 float 타입의 Python 스칼라와 float 타입의 Numeric 배열을 곱하면, Python의 float 타입은 실제로 double이므로 Numeric 배열이 double로 변환됩니다. 이렇게 되면 Numeric 배열의 크기가 두 배로 늘어나므로 원하는 동작이 아닌 경우가 많으며, 특히 배열이 매우 크다면 성가실 가능성이 높습니다. 저희는 같은 타입 부류, 즉 정수, 부동소수점, 복소수 안에서는 배열 타입이 Python 타입보다 우선하는 쪽을 선호합니다. 따라서 Python 정수와 Int16(short) 배열 사이의 연산은 Int16 배열을 반환합니다. 반면 Python float와 Int16 배열 사이의 연산은 Float64(double) 배열을 반환합니다. 두 배열 사이의 연산에는 일반적인 강제 변환 규칙이 적용됩니다.
- 정수 나눗셈은 어떻게 처리합니까?
앞으로 나올 Python 버전에서는 정수 나눗셈의 동작이 바뀝니다. 피연산자가 float로 변환되므로 결과도 float가 됩니다. 배열이 Python 스칼라보다 우선하는 스칼라 강제 변환 규칙을 제안대로 구현하면, 배열을 정수로 나눴을 때 정수 배열이 반환되어 double 타입 배열을 반환할 앞으로의 Python 버전과 일관되지 않게 됩니다. 과학 분야 프로그래머들은 정수 나눗셈과 부동소수점 나눗셈의 차이에 익숙하므로, Numeric 2는 이 동작을 계속 유지해야 합니까?
- 레코드는 어떻게 구현해야 합니까?
레코드를 구현하는 방법은 관점에 따라 두 가지가 있습니다. 첫 번째는 타입의 동작에 따라 배열을 별개의 클래스로 나누는 방법입니다. 예를 들어 클래스마다 연산의 범위와 종류가 다르므로, 숫자 배열이 한 클래스, 문자열이 두 번째 클래스, 레코드가 세 번째 클래스가 됩니다. 따라서 레코드 배열은 새로운 타입이 아니라 더 유연한 형태의 배열을 위한 메커니즘입니다. 이런 복잡한 데이터에 쉽게 접근하고 조작할 수 있도록, 이 클래스는 데이터 버퍼 안에서 서로 다른 바이트 오프셋을 가진 숫자 배열들로 구성됩니다. 예를 들어 Int16, Real32 값의 배열로 이루어진 테이블이 있을 수 있습니다. 오프셋이 0바이트이고 스트라이드가 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 등록, 정렬·바이트 스와핑·강제 변환 규칙을 포함한 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에는 배열과 Python 숫자 타입에 함께 적용되는 강제 변환 규칙이 한 가지뿐입니다. 이 때문에 배열 표현식을 계산하는 도중에 예상치 못한 성가신 문제가 생길 수 있습니다. Numeric 2는 강제 변환 규칙을 두 가지로 두어 이러한 문제를 해결하려고 합니다. 하나는 배열과 Python 숫자 타입에 적용되는 규칙이고, 다른 하나는 배열에만 적용되는 규칙입니다.
- 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의 typecode 방식은 하위 호환성을 위해 계속 사용할 수 있지만, 폐지 예정입니다.
부록
- 암시적 하위 배열 순회
컴퓨터 애니메이션은 모양이 동일한 여러 장의 2차원 이미지, 즉 프레임으로 구성됩니다. 이 이미지들을 하나의 메모리 블록에 쌓으면 3차원 배열이 만들어집니다. 그러나 수행할 연산은 3차원 배열 전체가 아니라 2차원 부분 배열의 집합을 대상으로 합니다. 대부분의 배열 언어에서는 for와 비슷한 루프를 사용하여 각 프레임을 추출하고, 연산을 적용한 다음, 출력 배열에 다시 삽입해야 합니다. J 언어는 프레임과 배열에 각각 랭크(rank)를 두어 프로그래머가 이러한 연산을 암묵적으로 수행할 수 있게 합니다. 기본적으로 배열을 생성할 때 이 랭크들은 서로 같습니다. J 언어에 기반을 두고 있으므로, Numeric 1 개발자들은 이 기능을 구현하려고 했습니다. Numeric 1 코드에는 이 동작을 구현하는 데 필요한 변수가 있지만, 실제로 구현된 적은 없습니다. Numeric 1의 배열 브로드캐스팅 규칙이 이 동작을 완전히 지원하지 못한다면, 저희는 Numeric 2에서 암묵적 부분 배열 반복을 구현할 계획입니다.
Copyright
This document is placed in the public domain.