[Withdrawn] PEP 296 - Adding a bytes Object Type
원문 링크: PEP 296 - Adding a bytes Object Type
상태: Withdrawn 유형: Standards Track 작성일: 12-Jul-2002
PEP 296은 Python 2.3 버전을 위해 bytes
객체 타입 추가를 제안했던 문서입니다. 이 제안은 나중에 PEP 358로 대체되어 철회(Withdrawn)되었습니다.
PEP 296 – bytes
객체 타입 추가
공지 (Notice)
이 PEP는 작성자에 의해 철회되었습니다 (PEP 358 선호).
개요 (Abstract)
이 PEP는 ‘bytes’라는 새로운 표준 타입과 내장 생성자(builtin constructor) 생성을 제안합니다. bytes
객체는 효율적으로 저장되는 바이트 배열이며, 유사한 다른 구현체들과 차별화되는 몇 가지 추가적인 특성을 가집니다.
배경 (Rationale)
현재 Python에는 이 제안의 bytes
객체와 유사한 기능을 구현하는 많은 객체가 있습니다. 예를 들어, 표준 string
, buffer
, array
, 그리고 mmap
객체는 모두 bytes
` 객체와 여러 면에서 매우 유사합니다. 또한, 여러 중요한 서드파티 확장 기능들이 유사한 요구를 충족하기 위해 비슷한 객체를 만들었습니다. 안타깝게도, 이러한 각 객체는 범위가 너무 좁고 더 넓은 범주의 문제에 적용하기에 중요한 기능들이 부족합니다.
명세 (Specification)
bytes
객체는 다음과 같은 중요한 특성을 가집니다.
- 효율적인 내부 배열 저장: 표준 C 타입인 “unsigned char”를 통해 효율적인 내부 배열 저장을 제공합니다. 이를 통해 할당되는 메모리 양을 세밀하게 제어할 수 있습니다. 다음 항목에 명시된 정렬(alignment) 제약 조건과 함께, 저수준(low level) 확장 기능이 필요에 따라 포인터를 다른 타입으로 캐스팅하는 것이 쉽습니다.
- 객체가 바이트 배열로 구현되어 있으므로, 현재 문자열(string)과 함께 작동하는 표준 라이브러리의 광범위한 루틴 라이브러리에
bytes
객체를 전달할 수 있습니다. 예를 들어,struct
모듈과 함께bytes
객체를 사용하면 순수 Python 스크립트만으로array
모듈을 완전히 대체할 수 있습니다. - 네이티브 8비트
unsigned
타입이 없는 특이한 플랫폼이 발견된다 하더라도, 객체는 Python 스크립트 레벨에서 8비트unsigned
값의 배열인 것처럼 자신을 표현하기 위해 최선을 다할 것입니다. 많은 확장 기능이 이를 올바르게 처리할지는 의문이지만, 이러한 경우 Python 스크립트는 이식성(portable)을 가질 수 있습니다.
- 객체가 바이트 배열로 구현되어 있으므로, 현재 문자열(string)과 함께 작동하는 표준 라이브러리의 광범위한 루틴 라이브러리에
- 할당된 바이트 배열의 정렬:
malloc
의 플랫폼 구현이 약속하는 대로 정렬됩니다. 확장 기능에서 생성된bytes
객체는 확장 기능 작성자가 적절하다고 판단하는 임의의 정렬을 제공할 수 있습니다.- 이러한 정렬 제약 조건은
bytes
객체가 모든 표준 C 타입, 예를 들어PyComplex
객체나 표준 C 타입으로 구성된 다른struct
들의 저장소로 사용될 수 있도록 허용해야 합니다. 추가적인 정렬 제약 조건은 필요에 따라 확장 기능에서 제공될 수 있습니다.
- 이러한 정렬 제약 조건은
- 시퀀스 연산(Sequence Operations) 지원:
bytes
객체는string
/array
객체에서 제공하는 시퀀스 연산의 부분 집합을 구현하지만, 일부 경우에 약간 다른 의미(semantics)를 가집니다.- 특히, 슬라이스(slice)는 항상 새로운
bytes
객체를 반환하지만, 내부 메모리는 두 객체 간에 공유됩니다. 이러한 종류의 슬라이스 동작은 “뷰(view)”를 생성한다고 불립니다. - 또한,
bytes
객체에 대한 반복(repetition) 및 연결(concatenation)은 정의되지 않으며 예외(exception)를 발생시킵니다. - 이러한 객체는 고성능 애플리케이션에서 사용될 가능성이 높기 때문에, 뷰 슬라이싱(view slicing) 사용 결정의 동기 중 하나는
bytes
객체 간의 복사(copying)가 매우 효율적이어야 하며 임시 객체 생성을 필요로 하지 않아야 한다는 것입니다. 다음 코드는 이를 보여줍니다.# create two 10 Meg bytes objects b1 = bytes(10000000) b2 = bytes(10000000) # copy from part of one to another with out creating a 1 Meg temporary b1[2000000:3000000] = b2[4000000:5000000]
- Rvalue의 길이가 Lvalue의 길이와 같지 않은 슬라이스 할당(slice assignment)은 예외를 발생시킵니다. 그러나 슬라이스 할당은 겹치는 슬라이스(memmove로 일반적으로 구현됨)에서도 올바르게 작동합니다.
- 특히, 슬라이스(slice)는 항상 새로운
- Pickle 및 cPickle 지원:
bytes
객체는 효율적인 직렬화(serialization)를 위해pickle
및cPickle
모듈에 의해 네이티브 타입으로 인식됩니다. (실제로, 이것은 서드파티 확장 기능으로는 구현할 수 없는 유일한 요구 사항입니다.)bytes
와 유사한 객체에 저장된 데이터를 임시 복사본을 문자열로 만들지 않고 직렬화해야 하는 요구를 해결하기 위한 부분적인 솔루션이 과거에 구현되었습니다.array
객체의tofile
및fromfile
메서드가 좋은 예시입니다.bytes
객체도 이러한 메서드를 지원할 것입니다.- 그러나 pickling은
shelve
모듈이나 Python 객체의 RPC 구현과 같은 다른 상황에서도 유용하며, 효율적인 데이터 전송을 위해 최종 사용자가 두 가지 다른 직렬화 메커니즘을 사용하도록 요구하는 것은 바람직하지 않습니다. - TODO: 이전 버전의 Python이
bytes
객체를string
객체로 언피클(unpickle)하도록 새로운bytes
객체의 pickling을 구현하려고 노력할 것입니다. - 언피클링 시,
bytes
객체는 Python에서 할당된 메모리(malloc을 통해)로부터 생성됩니다. 따라서 확장 기능이 제공한 포인터가 제공했을 수 있는 추가 속성(특수 정렬 또는 특수 메모리 타입)을 잃게 됩니다. - TODO:
bytes
타입의 C 서브클래스가 언피클될 메모리를 제공할 수 있도록 만들려고 노력할 것입니다. 예를 들어,PageAlignedBytes
라는 파생 클래스는 페이지 정렬된 메모리로 언피클될 것입니다. int
가 32비트인 모든 플랫폼(대부분의 경우)에서, 현재 31비트로 표현할 수 있는 길이보다 큰 문자열을 생성하는 것은 불가능합니다. 따라서 연산이 불가능할 때string
으로 pickling하는 것은 예외를 발생시킵니다.- 적어도 대용량 파일(large file)을 지원하는 플랫폼(대부분)에서는
file.write()
메서드를 반복적으로 호출하여 대용량bytes
객체를 파일로 pickling하는 것이 가능해야 합니다.
- PyBufferProcs 인터페이스 지원:
bytes
타입은PyBufferProcs
인터페이스를 지원하지만,bytes
객체는bytes
객체에 대한 참조가 유지되는 한 포인터가 할당 해제(deallocated)되거나 재할당(reallocated)되지 않는다는 추가적인 보장을 제공합니다.- 이는
bytes
객체가 일단 생성되면 크기 변경이 불가능하다(not resizable)는 것을 의미하지만,PyBytes_Check(...)
테스트가 통과되면 별도의 스레드가 가리키는 메모리를 조작하는 동안 전역 인터프리터 락(GIL)을 해제할 수 있도록 허용합니다. bytes
객체의 이러한 특성은 비동기 파일 I/O 또는 다중 프로세서(multiprocessor) 머신과 같이PyBufferProcs
에 의해 얻은 포인터가 전역 인터프리터 락과 독립적으로 사용되는 상황에서 사용될 수 있도록 합니다.- GIL이 해제된 후 포인터가 재할당되거나 해제될 수 없다는 것을 알면 확장 기능 작성자는 진정한 동시성(concurrency)을 얻고 포인터에 대한 장기 실행 계산을 위해 추가 프로세서를 사용할 수 있습니다.
- C/C++ 확장 기능에서,
bytes
객체는 제공된 포인터(pointer)와 소멸자(destructor) 함수로부터 생성될 수 있으며, 참조 카운트(reference count)가 0이 되면 메모리를 해제합니다. bytes
객체의 슬라이싱에 대한 특별한 구현은 여러bytes
객체가 동일한 포인터/소멸자를 참조할 수 있도록 합니다. 따라서 실제 포인터/소멸자에 대한 참조 카운트가 유지됩니다. 이 참조 카운트는 일반적으로 Python 객체와 관련된 참조 카운트와는 별개입니다.- TODO: 내부 참조 카운트 객체를 실제 Python 객체로 노출하는 것이 바람직할 수 있습니다. 좋은 사용 사례가 발생하면, 나중에 하위 호환성 손실 없이 구현하는 것이 가능해야 합니다.
- 이는
- 읽기 전용(
readonly
)bytes
객체:bytes
객체를 읽기 전용으로 표시하는 것도 가능하며, 이 경우 실제로는 변경 불가능(immutable)하지만bytes
객체의 다른 기능들을 제공합니다. - LONG_LONG 타입 길이 추적:
bytes
객체는 PythonLONG_LONG
타입으로 데이터의 길이를 추적합니다.PyBufferProcs
의 현재 정의가 길이를int
크기로 제한하더라도, 이 PEP는 그 부분에 변경을 제안하지 않습니다. 대신, 확장 기능은 명시적인PyBytes_Check(...)
호출을 통해 이 제한을 우회할 수 있으며, 성공하면PyBytes_GetReadBuffer(...)
또는PyBytes_GetWriteBuffer
호출을 통해 객체의 포인터와 전체 길이를LONG_LONG
으로 얻을 수 있습니다. - 예외 발생: 표준
PyBufferProcs
메커니즘이 사용되고bytes
객체의 크기가int
로 표현할 수 있는 크기보다 큰 경우bytes
객체는 예외를 발생시킬 것입니다. - Python 스크립팅에서:
bytes
객체는long
으로 인덱싱(subscriptable)될 수 있어 32비트int
제한을 피할 수 있습니다.len()
함수는PyObject_Size()
이며int
를 반환하므로 여전히 문제가 있습니다. 해결책으로bytes
객체는long
을 반환하는.length()
메서드를 제공할 것입니다.
- 생성자:
bytes
객체는 Python 스크립팅 레벨에서 할당할 바이트 수를int
/long
으로bytes
생성자에 전달하여 생성할 수 있습니다. 예를 들면:b = bytes(100000) # alloc 100K bytes
- 생성자는 다른
bytes
객체를 인자로 받을 수도 있습니다. 이것은 언피클링 구현 및 읽기/쓰기bytes
객체를 읽기 전용으로 변환하는 데 유용할 것입니다. 선택적 두 번째 인수는 읽기 전용bytes
객체 생성을 지정하는 데 사용됩니다.
- 생성자는 다른
- C API: C API에서
bytes
객체는 다음 시그니처 중 하나를 사용하여 할당할 수 있습니다.PyObject* PyBytes_FromLength(LONG_LONG len, int readonly); PyObject* PyBytes_FromPointer(void* ptr, LONG_LONG len, int readonly void (*dest)(void *ptr, void *user), void* user);
PyBytes_FromPointer(...)
함수에서dest
함수 포인터가NULL
로 전달되면 호출되지 않습니다. 이는 정적으로 할당된 공간에서bytes
객체를 생성하는 데만 사용해야 합니다.user
포인터는 다른 곳에서 클로저(closure)라고 불렸습니다. 이는 사용자가 어떤 목적으로든 사용할 수 있는 포인터입니다. 정리 시 소멸자 함수로 전달되며 여러 가지 유용하게 사용될 수 있습니다.user
포인터가 필요 없으면NULL
을 전달해야 합니다.
- 새로운 스타일 클래스:
bytes
타입은 모든 표준 Python 타입이 향하는 방향인 새로운 스타일 클래스(new style class)가 될 것입니다.
기존 타입과의 대조 (Contrast to existing types)
string
객체:bytes
객체가 없었던 문제를 해결하는 가장 일반적인 방법은string
객체를 대신 사용하는 것이었습니다. 바이너리 파일,struct
/array
모듈 및 기타 여러 예시가 있습니다. 이러한 사용이 일반적으로 텍스트 문자열과 관련이 없다는 스타일 문제를 제쳐두고,string
이 변경 불가능(immutable)하여 이러한 경우 반환된 데이터를 직접 조작할 수 없다는 실제 문제가 있습니다. 또한string
모듈의 수많은 최적화(예: 해시 값 캐싱 또는 포인터 인턴화)는 확장 기능 작성자가string
객체의 규칙을 어기려고 하면 매우 위험한 상황에 처하게 된다는 것을 의미합니다.buffer
객체:buffer
객체는bytes
객체가 충족하려는 목적을 다루기 위해 의도된 것처럼 보이지만, 구현의 몇 가지 단점 [1]으로 인해 많은 일반적인 경우에서 덜 유용했습니다.buffer
객체는 슬라이싱 동작에 대해 다른 선택을 했습니다(슬라이싱 및 다른 연산에 대해buffer
대신 새 문자열을 반환함). 또한bytes
객체가 하는 정렬(alignment) 또는 GIL 해제에 대한 많은 약속을 하지 않습니다.- 또한
buffer
객체와 관련하여,buffer
객체를bytes
객체로 단순히 교체하고 하위 호환성(backward compatibility)을 유지하는 것은 불가능합니다.buffer
객체는 다른 객체의PyBufferProcs
가 제공하는 포인터를 가져와 자신의 것으로 제공하는 메커니즘을 제공합니다. 다른 객체의 동작이bytes
객체가 따르는 것과 동일한 엄격한 규칙을 따르도록 보장할 수 없으므로,bytes
객체가 사용될 수 있는 곳에서는 사용할 수 없습니다.
- 또한
array
모듈:array
모듈은 바이트 배열 생성을 지원하지만, 확장 기능이 제공하는 메모리에 포인터와 소멸자를 제공하는 C API를 제공하지 않습니다. 이로 인해 공유 메모리(shared memory) 또는 DMA 전송과 같은 특수한 정렬 또는 잠금을 가진 메모리에서 객체를 구성하는 데 사용할 수 없습니다. 또한array
객체는 현재 pickling을 지원하지 않습니다. 마지막으로array
객체는extend
메서드를 통해 내용이 커질 수 있으므로, GIL이 유지되지 않은 채 사용되는 경우 포인터가 변경될 수 있습니다.array
객체에서buffer
객체를 생성하는 것은array
객체의 크기가 조정될 때 유효하지 않은 포인터를 남기는 동일한 문제를 가지고 있습니다.
mmap
객체:mmap
객체는 특정 틈새 시장(niche)을 만족시키지만, 더 넓은 범주의 문제를 해결하려고 시도하지 않습니다.- 서드파티 확장: 표준 Python 타입의 임시 객체를 생성하지 않고는 어떤 서드파티 확장도 pickling을 구현할 수 없습니다. 예를 들어, Numeric 커뮤니티에서는 대규모
array
가array
데이터를 복제하기 위해 대규모 바이너리 문자열을 생성하지 않고는 pickling할 수 없다는 것이 불쾌한 일입니다.
하위 호환성 (Backward Compatibility)
작성자가 인지하고 있는 하위 호환성 문제의 유일한 가능성은 새로운 bytes
타입을 포함하는 데이터를 언피클(unpickle)하려는 이전 버전의 Python에 있습니다.
참조 구현 (Reference Implementation)
TODO: 실제 구현이 진행 중이지만, 이 PEP가 추가 검토를 받음에 따라 변경될 가능성이 있습니다.
다음 새 파일이 Python baseline에 추가될 것입니다.
Include/bytesobject.h
(C 인터페이스)Objects/bytesobject.c
(C 구현)Lib/test/test_bytes.py
(단위 테스트)Doc/lib/libbytes.tex
(문서화)
다음 파일도 수정될 것입니다.
Include/Python.h
(bytesmodule.h 포함 파일 추가)Python/bltinmodule.c
(bytes 타입 객체 추가)Modules/cPickle.c
(표준 타입에 bytes 추가)Lib/pickle.py
(표준 타입에 bytes 추가)
여러 다른 모듈이 bytes
객체를 기반으로 정리되고 구현될 수 있습니다. mmap
모듈이 먼저 떠오르지만, 위에서 언급했듯이 array
모듈을 순수 Python 모듈로 재구현하는 것도 가능할 것입니다. 이 PEP가 실제로 소스 코드의 양을 어느 정도 줄일 수 있다는 점은 매력적이지만, 작성자는 이것이 기존 애플리케이션을 손상시키는 불필요한 위험을 초래할 수 있으므로 현재로서는 피해야 한다고 생각합니다.
추가 노트/코멘트 (Additional Notes/Comments)
- Guido van Rossum은
mmap
객체로부터bytes
객체를 생성할 수 있는 것이 합리적인지 궁금해했습니다.mmap
객체는bytes
객체에 메모리를 제공하는 데 필요한 요구 사항을 지원하는 것으로 보입니다. (크기가 조정되지 않고 포인터는 객체의 수명 동안 유효합니다.) 따라서mmap
모듈에bytes
객체를mmap
객체로부터 직접 생성할 수 있는 메서드를 추가할 수 있습니다. 이것이 어떻게 구현될지에 대한 초기 시도는 위에 설명된PyBytes_FromPointer()
함수를 사용하고mmap_object
를user
포인터로 전달하는 것입니다. 소멸자 함수는 정리 시mmap_object
의 참조 카운트를 감소(decref)시킬 것입니다. - Todd Miller는
PyObject_AsLargeReadBuffer()
및PyObject_AsLargeWriteBuffer
라는 두 개의 새로운 함수가 유용할 수 있다고 언급합니다. 이 함수들은PyObject_AsReadBuffer()
및PyObject_AsWriteBuffer()
와 유사하지만,void*
포인터 외에LONG_LONG
길이를 얻는 것을 지원합니다. 이 함수들은 확장 기능 작성자가bytes
객체(LONG_LONG 길이를 지원) 및 대부분의 다른 버퍼 유사 객체(int 길이만 지원)와 투명하게 작업할 수 있도록 허용할 것입니다. 이 함수들은 특정PyByte_GetReadBuffer()
및PyBytes_GetWriteBuffer()
함수를 생성하는 대신 또는 추가로 사용될 수 있습니다.- TODO: 작성자는 이것이 다른 객체들이 결국 대용량(64비트) 포인터를 지원할 수 있는 길을 열어주며
abstract.c
와abstract.h
에만 영향을 미칠 것이므로 매우 좋은 아이디어라고 생각합니다. 이것을 위에 추가해야 할까요?
- TODO: 작성자는 이것이 다른 객체들이 결국 대용량(64비트) 포인터를 지원할 수 있는 길을 열어주며
PyBufferProcs
인터페이스의 세그먼트 카운트(segment count)를 남용하여 길이의 31비트 제한을 우회하는 것은 좋은 해킹(hack)이 아니라는 점에 일반적으로 동의했습니다. 이것이 무엇을 의미하는지 모른다면, 당신은 좋은 동료들과 함께 있는 것입니다. Python baseline의 대부분 코드와 아마도 많은 서드파티 확장 기능에서 세그먼트 카운트가 1이 아닐 경우 포기합니다.
참조 (References)
- [1] The buffer interface
https://mail.python.org/pipermail/python-dev/2000-October/009974.html
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments