PEP 3137 – 불변 바이트와 가변 버퍼
- Author:
- Guido van Rossum <guido at python.org>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 26-Sep-2007
- Python-Version:
- 3.0
- Post-History:
- 26-Sep-2007, 30-Sep-2007
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
소개
가변 bytes 타입을 포함한 Python 3.0a1을 릴리스한 후, 불변 바이트를 표현하는 방법을 추가하라는 요구가 커졌습니다. Gregory P. Smith는 PEP 3118의 새로운 버퍼 API를 사용해 데이터 잠금을 요청함으로써 bytes 객체를 일시적으로 불변으로 만들 수 있게 하는 패치를 제안했습니다. 저는 이것이 올바른 접근 방식이 아니라고 생각했습니다.
그러자 Jeffrey Yasskin이 Adam Hupp의 도움을 받아 bytes 타입을 불변으로 만들고(값을 변경하는 API를 모두 거칠게 제거하는 방식으로) 그 여파로 테스트 스위트에서 생긴 문제를 수정하는 패치를 준비했습니다. 이를 통해 작은 조각들로 반환값을 만들어 가는 코드를 예외로 하면, bytes의 가변성에 의존하는 곳이 그리 많지 않다는 것이 드러났습니다.
그 결과를 곰곰이 생각해 보고, 배열(array) 모듈을 가변 bytes 타입의 대용품으로 사용하는 것이 이상적이지 않다는 점을 깨닫고, 또 Talin이 앞서 내놓았던 제안을 떠올리면서, 저는 가변 bytes 타입과 불변 bytes 타입을 모두 두자는 제안을 내놓았습니다. (이 제안은 이전에도 나온 적이 있었지만, Jeffrey의 패치라는 증거를 보기 전까지 저는 이 제안을 받아들일 생각이 없었습니다.)
게다가 가능한 구현 전략도 분명해졌습니다. 불변 bytes 타입에는 기존 PyString 구현에서 로케일 지원과 유니코드와의 암묵적 변환을 걷어 낸 것을 사용하고, 새 PyBytes 구현은 가변 bytes 타입으로 유지하는 것입니다.
이어진 논의에서 이 아이디어는 환영받지만 더 정확하게 명세해야 한다는 점이 분명해졌습니다. 그래서 이 PEP를 작성했습니다.
장점
불변 bytes 타입이 있으면 코드 객체가 이 타입을 사용할 수 있다는 장점이 있습니다. 또한 bytes를 키로 사용하는 해시 테이블을 효율적으로 만들 수 있습니다. 이는 텍스트를 나타내는 바이트에 기반한 HTTP나 SMTP 같은 프로토콜을 파싱할 때 유용할 수 있습니다.
Python 2.x에서 바이너리 데이터(또는 인코딩된 텍스트)를 다루는 코드를 이식하기는 가변 bytes를 사용하는 원래의 3.0 설계보다 새 설계에서 더 쉽습니다. str을 bytes로 바꾸고 ‘…’ 리터럴을 b’…’ 리터럴로 바꾸기만 하면 됩니다.
명명
Python 수준에서 다음과 같은 타입 이름을 제안합니다:
bytes는 바이트의 불변 배열입니다 (PyString)bytearray는 바이트의 가변 배열입니다 (PyBytes)memoryview는 다른 객체에 대한 바이트 뷰입니다 (PyMemory)
buffer라는 이름의 기존 타입은 PEP 3118에서 도입된 새 타입 memoryview와 매우 비슷하므로 불필요합니다. 이 PEP의 나머지 부분에서는 memoryview의 기능을 다루지 않습니다. 여기서는 단지 기존 buffer 타입을 없애는 것을 정당화하기 위해 언급할 뿐입니다. (이 PEP의 이전 버전에서는 PyBytes의 새 이름으로 buffer를 제안했지만, 버퍼라는 단어가 다른 여러 용도로 쓰인다는 점을 고려하여 결국 이 이름은 너무 혼란스럽다고 판단되었습니다.)
언젠가는 C API 이름을 바꾸는 것이 합리적이지만, 이 PEP에서는 누구에게나 익숙할 기존 C API 이름을 유지합니다.
요약
다음은 여러 Python 버전의 타입 이름을 요약한 간단한 ASCII 아트 표입니다:
+--------------+-------------+------------+--------------------------+
| C name | 2.x repr | 3.0a1 repr | 3.0a2 repr |
+--------------+-------------+------------+--------------------------+
| PyUnicode | unicode u'' | str '' | str '' |
| PyString | str '' | str8 s'' | bytes b'' |
| PyBytes | N/A | bytes b'' | bytearray bytearray(b'') |
| PyBuffer | buffer | buffer | N/A |
| PyMemoryView | N/A | memoryview | memoryview <...> |
+--------------+-------------+------------+--------------------------+
리터럴 표기법
Python 3.0a1에서 도입된 b’…’ 표기법은 어떤 변형을 사용하든 불변 bytes 객체를 반환합니다. 바이트의 가변 배열을 만들려면 bytearray(b’…’) 또는 bytearray([…])를 사용하십시오. 후자의 형태는 range(256) 범위의 정수로 이루어진 리스트를 받습니다.
기능
PEP 3118 버퍼 API
bytes와 bytearray는 모두 PEP 3118 버퍼 API를 구현합니다. bytes 타입은 읽기 전용 요청만 구현하지만, bytearray 타입은 쓰기 가능 요청과 데이터 잠금 요청도 허용합니다. 요소 데이터 타입은 항상 ‘B’(즉, 부호 없는 바이트)입니다.
생성자
bytes와 bytearray 모두에 적용되는 생성자에는 네 가지 형태가 있습니다:
bytes(<bytes>),bytes(<bytearray>),bytearray(<bytes>),bytearray(<bytearray>): 단순 복사 생성자입니다. 단,bytes(<bytes>)는 (불변인) 인자를 그대로 반환할 수 있지만,bytearray(<bytearray>)는 항상 복사본을 만듭니다.bytes(<str>, <encoding>[, <errors>]),bytearray(<str>, <encoding>[, <errors>]): 텍스트 문자열을 인코딩합니다.str.encode()메서드는 불변 bytes 객체를 반환한다는 점에 유의하십시오. <encoding> 인자는 필수이며, <errors>는 선택 사항입니다. <encoding>과 <errors>를 지정하는 경우 반드시str인스턴스여야 합니다.bytes(<memory view>),bytearray(<memory view>): PEP 3118 버퍼 API를 구현하는 모든 객체로부터 bytes 또는 bytearray 객체를 생성합니다.bytes(<iterable of ints>),bytearray(<iterable of ints>): range(256) 범위의 정수 스트림으로부터 bytes 또는 bytearray 객체를 생성합니다.bytes(<int>),bytearray(<int>): 주어진 길이의 0으로 초기화된 bytes 또는 bytearray 객체를 생성합니다.
비교
bytes 타입과 bytearray 타입은 서로 비교할 수 있고 순서도 매길 수 있으므로, 예를 들어 b’abc’ == bytearray(b’abc’) < b’abd’가 성립합니다.
두 타입 중 어느 쪽이든 str 객체와 동등 비교하면 두 피연산자의 내용과 관계없이 False를 반환합니다. str과의 순서 비교는 TypeError를 발생시킵니다. 이는 모두 호환되지 않는 타입의 객체 간 비교 및 순서 매기기에 관한 표준 규칙을 따르는 것입니다.
(참고: Python 3.0a1에서는 bytes 인스턴스를 str 인스턴스와 비교하면 TypeError가 발생했습니다. 이는 특히 Python 2.x에서 이식한 코드에서 가끔 발생하는 실수를 더 빨리 잡아낼 수 있으리라는 전제에 따른 것이었습니다. 그러나 python-3000 메일링 리스트에서 이루어진 긴 논의를 통해 이 방식의 문제점이 너무 많이 지적되어 명백히 나쁜 아이디어로 판명되었으므로, 이 PEP의 나머지 부분이 어떻게 되든 관계없이 3.0a2에서 되돌려질 예정입니다.)
슬라이싱
bytes 객체를 슬라이싱하면 bytes 객체를 반환합니다. bytearray 객체를 슬라이싱하면 bytearray 객체를 반환합니다.
bytearray 객체에 대한 슬라이스 대입은 PEP 3118 버퍼 API를 구현하는 모든 객체 또는 range(256) 범위의 정수로 이루어진 이터러블을 받습니다.
인덱싱
bytes와 bytearray를 인덱싱하면 작은 정수를 반환합니다(3.0a1의 bytes 타입, 그리고 리스트나 array.array(‘B’)와 마찬가지입니다).
bytearray 객체의 항목에 대한 대입은 range(256) 범위의 int를 받습니다. (bytes 시퀀스로부터 대입하려면 슬라이스 대입을 사용하십시오.)
Str()과 Repr()
str() 함수와 repr() 함수는 이 객체들에 대해 같은 것을 반환합니다. bytes 객체의 repr()은 b’…’ 형식의 리터럴을 반환합니다. bytearray의 repr()은 “bytearray(b’…’)” 형식의 문자열을 반환합니다.
연산자
다음 연산자들은 별도로 언급한 경우를 제외하고 bytes 타입과 bytearray 타입 모두에서 구현됩니다:
b1 + b2: 연결입니다. bytes/bytearray 피연산자가 섞여 있으면 반환 타입은 첫 번째 인자의 타입을 따릅니다(+=가 어떻게 동작하는지 생각해 보기 전까지는 임의적인 것처럼 보입니다).b1 += b2: b1이 bytearray 객체이면 b1을 변경합니다.b * n,n * b: 반복입니다. n은 정수여야 합니다.b *= n: b가 bytearray 객체이면 b를 변경합니다.b1 in b2,b1 not in b2: 부분 문자열 검사입니다. b1은 PEP 3118 버퍼 API를 구현하는 어떤 객체든 될 수 있습니다.i in b,i not in b: 단일 바이트 멤버십 검사입니다. i는 정수여야 합니다(길이가 1인 bytes 배열이면 부분 문자열 검사로 간주되며, 결과는 같습니다).len(b): 바이트 수입니다.hash(b): 해시 값입니다. bytes 타입만 구현합니다.
% 연산자는 구현되지 않는다는 점에 유의하십시오. 그만한 복잡성을 감수할 가치가 없어 보입니다.
메서드
다음 메서드는 bytes와 bytearray 모두 비슷한 의미로 구현합니다. 이 메서드들은 bytes 인자로 PEP 3118 버퍼 API를 구현하는 것이면 무엇이든 받아들이며, 메서드가 호출된 객체(“self”)와 같은 타입을 반환합니다.:
.capitalize(), .center(), .count(), .decode(), .endswith(),
.expandtabs(), .find(), .index(), .isalnum(), .isalpha(), .isdigit(),
.islower(), .isspace(), .istitle(), .isupper(), .join(), .ljust(),
.lower(), .lstrip(), .partition(), .replace(), .rfind(), .rindex(),
.rjust(), .rpartition(), .rsplit(), .rstrip(), .split(),
.splitlines(), .startswith(), .strip(), .swapcase(), .title(),
.translate(), .upper(), .zfill()
이는 .encode()를 제외하면 Python 2.x의 str 타입에 있는 메서드 집합과 정확히 같습니다. 시그니처와 의미도 같습니다. 다만 문자, 공백, 소문자와 같은 문자 클래스가 사용될 때는 언제나 이 클래스들의 ASCII 정의가 사용됩니다. (Python 2.x의 str 타입은 locale 모듈을 통해 설정할 수 있는 현재 로케일의 정의를 사용합니다.) .encode() 메서드는 Python 3000에서 인코딩과 디코딩의 정의가 더 엄격해졌기 때문에 제외되었습니다. 인코딩은 항상 유니코드 문자열을 받아 bytes 시퀀스를 반환하고, 디코딩은 항상 bytes 시퀀스를 받아 유니코드 문자열을 반환합니다.
또한 두 타입 모두 클래스 메서드 .fromhex()를 구현합니다. 이 메서드는 16진수 값이 담긴 문자열(바이트 사이에 공백이 있든 없든)로부터 객체를 생성합니다.
bytearray 타입은 MutableSequence ABC(PEP 3119 참고)의 다음 메서드를 추가로 구현합니다.
.extend(), .insert(), .append(), .reverse(), .pop(), .remove().
bytes와 str 타입
Python 3.0a1의 bytes 타입과 마찬가지로, 그리고 Python 2.x에서의 str과 unicode(유니코드)의 관계와는 달리, 인코딩을 지정하지 않고 bytes(또는 bytearray) 객체와 str 객체를 섞으려고 하면 TypeError 예외가 발생합니다. (단, bytes/bytearray 객체와 str 객체를 동등 비교하면 단순히 False를 반환합니다. 위의 비교 절을 참고하십시오.)
bytes 또는 bytearray 객체와 str 객체 사이의 변환은 항상 인코딩을 사용하여 명시적으로 해야 합니다. 동등한 API가 두 가지 있습니다. str(b, <encoding>[, <errors>])는 b.decode(<encoding>[, <errors>])와 동등하고, bytes(s, <encoding>[, <errors>])는 s.encode(<encoding>[, <errors>])와 동등합니다.
한 가지 예외가 있습니다. str(b)라고 작성하면 인코딩을 지정하지 않고도 bytes(또는 bytearray)를 str로 변환할 수 있습니다. 이는 repr(b)와 같은 결과를 냅니다. 이 예외가 필요한 이유는 어떤 객체든 출력할 수 있다는 일반적인 약속이 있고, 출력은 str로 변환하는 것의 특수한 경우일 뿐이기 때문입니다. 하지만 (Python 2.x와 달리) bytes 객체를 출력할 때 개별 바이트를 문자로 해석한다는 약속은 없습니다.
현재 str 타입은 PEP 3118 버퍼 API를 구현합니다. 이것이 가끔은 편리할 수도 있지만, 혼란을 줄 가능성도 있습니다. 버퍼 API로 접근하는 바이트는 플랫폼에 따라 달라지는 인코딩을 나타내기 때문입니다. 플랫폼의 바이트 순서와 컴파일 시점 설정 옵션에 따라 인코딩은 UTF-16-BE, UTF-16-LE, UTF-32-BE, UTF-32-LE 중 하나가 될 수 있습니다. 더 나쁜 점은, str 타입의 다른 구현이 바이트 표현을 완전히 바꾸거나(예: UTF-8로), 아예 데이터를 연속된 바이트 배열로 접근하는 것 자체를 불가능하게 만들 수도 있다는 것입니다. 따라서 PEP 3118 버퍼 API는 str 타입에서 제거될 것입니다.
basestring 타입
basestring 타입은 언어에서 제거될 것입니다. 기존에 isinstance(x, basestring)라고 작성하던 코드는 대신 isinstance(x, str)를 사용하도록 변경해야 합니다.
피클링
독자의 연습 문제로 남겨 둡니다.
Copyright
This document has been placed in the public domain.