PEP 3137 – 不可变字节和可变缓冲区
- 作者:
- Guido van Rossum <guido at python.org>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2007年9月26日
- Python 版本:
- 3.0
- 历史记录:
- 2007年9月26日,2007年9月30日
简介
在 Python 3.0a1 发布了一个可变字节类型后,人们开始施压要求添加一种表示不可变字节的方式。Gregory P. Smith 提出了一个补丁,允许通过使用来自 PEP 3118 的新的缓冲区 API 请求锁定数据来使字节对象暂时不可变。在我看来,这并不是正确的方法。
随后,Jeffrey Yasskin 在 Adam Hupp 的帮助下,准备了一个补丁,使字节类型不可变(通过粗略地移除所有可变 API)并在测试套件中修复了由此产生的问题。这表明,除了构建由小片段组成的返回值的代码之外,没有太多地方依赖于字节的可变性。
在仔细思考了后果之后,注意到使用数组模块作为一种临时的可变字节类型远非理想,并回想起 Talin 之前提出的一个建议,我提出了一个建议,即同时拥有可变和不可变的字节类型。(之前也提出过这个建议,但在看到 Jeffrey 的补丁的证据之前,我并不接受这个建议。)
此外,一个可能的实现策略变得清晰:使用旧的 PyString 实现,去除本地化支持和到/从 Unicode 的隐式转换,作为不可变字节类型,并保留新的 PyBytes 实现作为可变字节类型。
随后的讨论表明,这个想法是受欢迎的,但需要更精确地指定。因此有了这个 PEP。
优势
拥有不可变字节类型的一个优势是代码对象可以使用它们。它还使得能够使用字节作为键有效地创建哈希表;这在解析像 HTTP 或 SMTP 这样的基于字节表示文本的协议时可能很有用。
使用新的设计比使用最初的 3.0 设计(具有可变字节)更容易移植在 Python 2.x 中操作二进制数据(或编码文本)的代码;只需将 str
替换为 bytes
并将 ‘…’ 字面量更改为 b’…’ 字面量。
命名
我建议在 Python 级别使用以下类型名称
bytes
是一个不可变的字节数组 (PyString)bytearray
是一个可变的字节数组 (PyBytes)memoryview
是另一个对象的字节视图 (PyMemory)
旧的类型名为 buffer
,与由 PEP 3118 引入的新类型 memoryview
非常相似,因此它是冗余的。本 PEP 的其余部分不讨论 memoryview
的功能;它只是在这里被提及以证明去除旧的 buffer
类型是合理的。(本 PEP 的早期版本建议将 buffer
作为 PyBytes 的新名称;最后,考虑到 buffer 这个词的许多其他用法,这个名称被认为令人困惑。)
虽然最终更改 C API 名称是有意义的,但本 PEP 保留了旧的 C API 名称,这些名称应该为所有人所熟悉。
总结
以下是一个简单的 ASCII 艺术表格,总结了不同 Python 版本中的类型名称
+--------------+-------------+------------+--------------------------+
| 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’…’ 表示法返回一个不可变的字节对象,无论使用哪种变体。要创建一个可变的字节数组,请使用 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()
方法返回一个不可变的字节对象。<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>)
:构造一个给定长度的、用零初始化的 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) 内的整数。(要从字节序列赋值,请使用切片赋值。)
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 的字节数组,则将其视为子字符串测试,结果相同)。len(b)
:字节数。hash(b)
:哈希值;仅由 bytes 类型实现。
请注意,% 运算符未实现。它看起来不值得增加复杂性。
方法
以下方法由 bytes 和 bytearray 实现,语义相似。对于字节参数,它们接受任何实现 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()
这正是 Python 2.x 中 str 类型上存在的方法集,排除了 .encode()。签名和语义也是相同的。但是,无论何时使用字符类(如字母、空格、小写),都使用这些类的 ASCII 定义。(Python 2.x str 类型使用来自当前区域设置的定义,可以通过区域设置模块进行设置。).encode() 方法被排除在外,因为 Python 3000 中对编码和解码的定义更严格:编码始终接受 Unicode 字符串并返回字节序列,解码始终接受字节序列并返回 Unicode 字符串。
此外,这两种类型都实现了类方法 .fromhex()
,该方法根据包含十六进制值的字符串(字节之间有或没有空格)构造一个对象。
bytearray 类型实现了来自可变序列 ABC 的这些附加方法(参见 PEP 3119)
.extend()、.insert()、.append()、.reverse()、.pop()、.remove()。
字节和 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 的一个特例。但是,并没有承诺打印 bytes 对象会将各个字节解释为字符(与 Python 2.x 中不同)。
str 类型目前实现了 PEP 3118 缓冲区 API。虽然这有时可能很方便,但也可能令人困惑,因为通过缓冲区 API 访问的字节表示平台相关的编码:根据平台字节序和编译时配置选项,编码可能是 UTF-16-BE、UTF-16-LE、UTF-32-BE 或 UTF-32-LE。更糟糕的是,str 类型的不同实现可能会完全改变字节表示,例如转换为 UTF-8,甚至使完全无法将数据作为连续的字节数组访问。因此,将从 str 类型中删除 PEP 3118 缓冲区 API。
basestring
类型
将从语言中删除 basestring
类型。以前使用 isinstance(x, basestring)
的代码应更改为使用 isinstance(x, str)
代替。
序列化
留作读者练习。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3137.rst
上次修改时间:2023-09-09 17:39:29 GMT