7-1-unicode-和字节字符串
1. 字符串基础知识
1.1 字符编码方法
大多数程序员把字符串看作是用来表示文本数据的一系列字符。但是,根据必须记录何种字符集,计算机内存中存储字符的方式有所不同。
ASCII 标准在美国创建,并且定义了大多数美国程序员使用的文本字符串表示法。ASCII 定义了从0到127的字符代码,并且允许每个字符存储在一个8位的字节中(实际上,只有其中的7位真正用到)。例如,ASCII 标准把字符 'a' 映射为整数值 97 (十六进制中的0x61),它存储在内存和文件的一个单个字节中:
ord('a')
97
hex(97)
'0x61'
chr(97)
'a'
然而,有时候每个字符一个字节并不够。例如,各种符号和重音字符并不在 ASCII 所定义的可能字符的范围中。为了容纳特殊字符,一些标准允许一个8位字节中的所有可能的值(即0到255)来表示字符,并且把(ASCII 范围之外的)值128到255分配给特殊字符。这样的一个标准叫做 Latin-1,广泛地用于西欧地区。在 Latin-1 中,127以上的字符代码分配给了重音和其他特殊字符。例如,分配给字节值196的字符,是一个特殊标记的非ASCII字符:
0xC4
196
chr(196)
'Ä'
Unicode 考虑到更多的灵活性。Unicode 文本通常叫做“宽字符”字符串,因为每个字符可能表示为多个字节。Unicode 通常用在国际化的程序中,以表示欧洲和亚洲的字符集,它们往往拥有比8位字节所能表示的更多的字符。
要在计算机内存中存储如此丰富的文本,我们要确保字符与原始字节之间可以使用一种编码相互转换,而编码就是把一个 Unicode 字符转换为字节序列以及从一个字节序列提取字符串的规则。更程序化地说,字节和字符串之间的来回转换由两个术语定义:
- 编码:根据一个想要的编码名称,把一个字符串翻译为其原始字节形式。
- 解码:根据其编码名称,把一个原始字节串翻译为字符串形式的过程。
也就是说,我们从字符串编码为原始字节,并且从原始字节解码为字符串。
广为使用的 UTF-8 编码,通过采用可变的字节数的方案,允许表示众多的字符。小于128的字符代码表示为单个字节;128和0x7ff (2047)之间的代码转换为两个字节,而每个字节拥有一个128到255之间的值;0x7ff以上的代码转换为3个或4个字节序列,序列中的每个字节的值在128到255之间。
由于编码的字符映射把字符分配给同样的代码以保持兼容性,因此 ASCII 是 Latin-1 和 UTF-8 的子集。
对于 Python 程序员来说,编码指定为包含了编码名的字符串。Python 带有大约100种不同的编码。导入 encodings 模块并运行 help(encodings) 也会显示很多的编码名称,一些是在Python中实现的,一些是在C中实现的,一些编码也有多个名称。
1.2 Python 如何在内存中存储字符串
前一节的编码实际上只适用于文本存储或传输到外部的文件和其他媒介中。在内存中,Python 总是以与编码无关的格式存储解码的文本字符串,对于每个字符,它可能使用或不使用多个字节。所有文本处理都以这种统一的内部格式进行。只有当被传输到或传输自外部文本文件、字节字符串或有特定编码需求的 API 时,文本才会被转换为特定的编码格式。然而,一旦在内存中,字符串就没有编码。它们只是本书中介绍的字符串对象。
Python 3.3 和更高版本使用的是可变长度方案,每个字符有 1、2 或 4 字节,具体取决于字符串的内容。大小是根据字符串中 Unicode 序号最大的字符来选择的。这种方案在通常情况下允许节省空间,但也允许在所有平台上使用完整的 UCS-4。
1.3 Python 字符串类型
Python 语言提供了字符串数据类型在脚本中表示字符文本。Python 3.X 带有 3 种字符串对象类型——一种用于文本数据,两种用于二进制数据:
str
表示 Unicode 文本(8位的和更宽的)。bytes
表示二进制数据。bytearray
,是一种可变的的bytes
类型。
Python 3 的 str
类型定义为一个不可改变的字符序列(不一定是字节),这可能是像ASCII这样的每个字符一个字节的常规文本,或者是像 UTF-8 Unicode 这样可能包含多字节字符的字符集文本。
尽管 Python 3 新的 str
类型确实实现了想要的字符串/Unicode结合,但很多程序仍然需要处理那些没有针对每个任意文本格式都编码的 raw 字节数据。图像和声音文件,以及用来与设备接口的打包数据,或者你想要用 Python 的 struct
模块处理的 C 程序,都属于这一类型。因此,为了支持真正的二进制数据的处理,还引入了一种新的类型,即 bytes
。
bytes
类型定义为一个 8 位整数的不可变序列,表示绝对的字节值。此外,Python 3 的 bytes
类型支持几乎 str
类型所做的所有相同操作:这包括字符串方法、序列操作,甚至 re 模块模式匹配;但是不包括字符串格式化。
一个 Python 3 bytes
对象其实只是较小整数的一个序列,其中每个整数的范围都在 0 到 255 之间;索引一个 bytes
将返回一个 int
,分片一个 bytes
将返回另一个 bytes
,并且在一个 bytes
上运行内置函数 list
将返回整数,而不是字符的一个列表。为了方便起见, bytes
对 象打印为字符串而不是整数。
Python 的开发者也在 Python 3 中添加了一个 bytearray
类型,bytearray
是 bytes
类型的一个变体,它是可变的并且支持原处修改。它支持 str
和 bytes
所支持的常见的字符串操作,以及和列表相同的很多原处修改操作(例如,append 和 extend 方法,以及向索引赋值)。
1.4 文本和二进制文件
文件 I/O(输入和输出)在 Python 3 中也有所改进,以反映 str/bytes 的区分以及对编码 Unicode 文本的自动支持。Python 现在在文本文件和二进制文件之间做了一个明显的独立于平台的区分:
- 文本文件
- 当一个文件以文本模式打开的时候,读取其数据会自动将其内容解码(每个平台一个默认的或一个提供的编码名称),并且将其返回为一个
str
,写入会接受一个str
,并且在将其传输到文件之前自动编码它。文本模式的文件还支持统一的行尾转换和额外的编码特定参数。根据编码名称,文本文件也自动处理文件开始处的字节顺序标记序列。 - 二进制文件
- 通过在内置的
open
调用的模式字符串参数添加一个b
(只能小写),以二进制模式打开一个文件的时候,读取其数据不会以任何方式解码它,而是直接返回其原始的未经修改的内容,作为一个bytes
对象;写入类似地接受一个bytes
对象,并且将其传送到文件中而未经修改。二进制模式文件也接受一个bytearray
对象作为写入文件中的内容。
由于 str
和 bytes
之间的语言差距明显,所以必须确定数据本质上是文本或二进制,并且在脚本中相应地使用 str
或 bytes
对象来表示其内容。最终,以何种模式打开一个文件将决定脚本使用何种类型的对象来表示其内容:
- 如果正在处理图像文件,其他程序创建的、而且必须解压的打包数据,或者一些设备数据流,则使用
bytes
和二进制模式文件处理它更合适。如果想要更新数据而不在内存中产生其副本,也可以选择使用bytearray
。 - 如果你要处理的内容实质是文本的内容,例如程序输出、HTML、国际化文本或 CSV 或 XML 文件,可能要使用
str
和文本模式文件。
2. Python 的字符串应用
2.1 字符串常量
当调用 str
或 bytes
这样的一个内置函数、通过调用 open
创建读取一个文件或在脚本中编写常量语法时,会引发 Python 3 的字符串对象。对于后者,一种新的常量形式 b'xxx'
(以及对等的 B'xxx'
)用来创建 Python 3 中的 bytes
对象,bytearray
对象可以通过调用 bytearray
函数来创建,这会带有各种可能的参数。
在 Python 3 中,所有当前字符串常量形式,'xxx'、"xxx" 和三引号字符串块,都产生一个 str
;在它们任何一种前面添加一个 b
或 B
,则会创建一个 bytes
。这个新的 b'…'
字节常量类似于用来抑制反斜杠转义的 r'…'
raw字符串。
B = b'spam' # bytes 常量创建一个 bytes 对象(8 位字节)
S = 'eggs' # str 常量创建一个 Unicode 文本字符串
type(B), type(S)
(bytes, str)
bytes
对象实际上是较小的整数的一个序列,尽管它尽可能地将自己的内容打印为字符:
B[0], S[0]
(115, 'e')
B[1:], S[1:]
(b'pam', 'ggs')
list(B), list(S)
([115, 112, 97, 109], ['e', 'g', 'g', 's'])
bytes
对象是不可修改的,就像 str
,我们可以把一个 str
、bytes
或整数赋给一个 bytes
对象的偏移。
B[0] = 'x'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-42963deafbaa> in <module>()
----> 1 B[0] = 'x'
TypeError: 'bytes' object does not support item assignment
S[0] = 'x'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-126ee69650f6> in <module>()
----> 1 S[0] = 'x'
TypeError: 'str' object does not support item assignment
bytes
前缀对于任何字符串常量形式也有效,包括三引号块。
B = B"""
xxx
yyy
"""
B
b'\nxxx\nyyy\n'
2.2 字符串类型转换
Python 3 中 str
和 bytes
类型对象不在表达式中自动地混合,并且当传递给函数的时候不会自动地相互转换。期待一个 str
对象作为参数的函数,通常不能接受一个 bytes
;反之亦然。
因此,Python 3 基本上要求遵守一种类型或另一种类型,或者手动执行显式转换:
str.encode()
和bytes(S, encoding)
把一个字符串转换为其 raw bytes 形式,并且在此过程中根据一个str
创建一个bytes
。bytes.decode()
和str(B, encoding)
把 raw bytes 转换为其字符串形式,并且在此过程中根据一个bytes
创建一个str
。
encode
和 decode
方法(以及文件对象)针对你的平台使用一个默认编码,或者一个显式传入的编码名:
S = 'eggs'
S.encode() # str -> bytes
b'eggs'
bytes(S, encoding='ascii')
b'eggs'
B = b'spam'
B.decode() # bytes -> str
'spam'
str(B, encoding='ascii')
'spam'
这里有两点要注意。首先,平台的默认编码在 sys
模块中可用,但是,bytes
的编码参数不是可选的,即便它在 str.encode
(和 bytes.decode
) 中亦是如此。
其次,尽管调用 str
并不像 bytes
那样要求编码参数,但在 str
调用中省略它并不意味着它是默认的,相反,不带编码的一个 str
调用返回 bytes
对象的打印字符串,而不是其 str
转换后的形式,假设 B 和 S 仍然和前面相同:
import sys
sys.platform
'win32'
sys.getdefaultencoding() # str 默认编码
'utf-8'
bytes(S)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-19-571c772a7654> in <module>()
----> 1 bytes(S)
TypeError: string argument without an encoding
str(B) # 不带 encoding,打印字符串,没有转换
"b'spam'"
len(str(B))
7
len(str(B, encoding='ascii')) # 使用 encoding 转换为 str
4
3. 编码 Unicode 字符串
要在字符串中编码任意的 Unicode 字符,有些字符可能甚至无法在键盘上输入,Python 的字符串常量支持 "\xNN" 十六进制字节值转义以及 "\uNNNN" 和 "\UNNNNNNNN" Unicode 转义。
3.1 编码 ASCII 文本
ASCII 文本是一种简单的 Unicode,存储为表示字符的字节值的一个序列:
ord('X')
88
chr(88)
'X'
S = 'XYZ'
[ord(c) for c in S]
[88, 89, 90]
3.2 编码非 ASCII 文本
要编码非 ASCII 字符,可能在字符串中使用十六进制或 Unicode 转义;十六进制转义限制于单个字节的值,但 Unicode 转义可以指定其值有两个和四个字节宽度的字符。也可以将其嵌入 Python 3 的 str 对象中:
chr(0xc4)
'Ä'
chr(0xe8)
'è'
S = '\xc4\xe8' # 单字节 8 位 16 进制转义
S
'Äè'
S = '\u00c4\u00e8' # 16 位 Unicode 转义
S
'Äè'
S = '\U000000c4\U000000e8' # 32 位 Unicode 转义
S
'Äè'
3.3 编码和解码非 ASCII 文本
如果我们试图把一个非 ASCII 字符串编码为 raw 字节以像 ASCII 一样使用,我们会得到一个错误,因为字符超出了 ASCII 的 7 位编码值范围:
S = '\u00c4\u00e8'
S
'Äè'
S.encode('ascii')
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
<ipython-input-10-6bb8f4428afe> in <module>()
----> 1 S.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
像 Latin-1 这样的编码是有效的,因为每个字符编码为 8 位,并且为每个字符分配一个字节;像 UTF-8 这样的编码为每个字符分配 2 个字节。如果把这个字符串写入一个文件,这里显示的 raw 字节就是针对给定的编码类型而实际存储在文件中的内容:
S.encode('latin-1')
b'\xc4\xe8'
S.encode('utf-8')
b'\xc3\x84\xc3\xa8'
也可以用其他的办法,从一个文件读入 raw 字节并且将其解码回一个 Unicode 字符串:
B = b'\xc4\xe8'
B.decode('latin-1')
'Äè'
B = b'\xc3\x84\xc3\xa8'
B.decode('utf-8')
'Äè'
3.4 其他 Unicode 编码技术
一些编码甚至使用较大的字节序列来表示字符。当需要的时候,我们可以为自己字符串中的字符指定 16 位或 32 位的 Unicode 值,对于前者使用 "\u…"
表示 4 个十六进制位,对于后者使用 "\U…."
表示 8 个十六进制位。
S = 'A\u00c4B\U000000e8C' # A、B、C 和两个非 ASCII 字符
S
'AÄBèC'
S.encode('latin-1')
b'A\xc4B\xe8C'
S.encode('utf-8')
b'A\xc3\x84B\xc3\xa8C'
3.5 字节字符串常量:编码文本
这里有两点要注意。首先,Python 3 允许特殊的字符以十六进制和 Unicode 转义的方式编码到 str
字符串中,但是,只能以十六进制转义的方式编码到 bytes
字符串中:Unicode 转义会默默地逐字转换为字节常量,而不是转义。实际上,bytes
必须编码为 str
字符串,以便正常地打印非 ASCII 字符:
S = 'A\xC4B\xE8C' # str 能识别十六进制和 Unicode 转义
S
'AÄBèC'
S = 'A\u00C4B\U000000E8C'
S
'AÄBèC'
B = b'A\xC4B\xE8C' # bytes 能识别十六进制但不能识别 Unicode
B
b'A\xc4B\xe8C'
B = b'A\u00C4B\U000000E8C'
B
b'A\\u00C4B\\U000000E8C'
B = b'A\xC4B\xE8C' # bytes 使用十六进制转义
print(B)
b'A\xc4B\xe8C'
B.decode('latin-1')
'AÄBèC'
其次,bytes
常量要求字符要么是 ASCII 字符,要么如果它们的值大于 127 就进行转义。另一方面,str
字符串允许常量包含源字符集中的任何字符(除非在源文件中给定一个编码声明,否则默认为 UTF-8):
S = 'AÄBèC'
S
'AÄBèC'
B = b'AÄBèC'
File "<ipython-input-30-796aad809d0f>", line 1
B = b'AÄBèC'
^
SyntaxError: bytes can only contain ASCII literal characters.
B = b'A\xC4B\xE8C' # 字符必须为 ASCII,否则需要转义
B
b'A\xc4B\xe8C'
B.decode('latin-1')
'AÄBèC'
S.encode() # 使用系统默认 UTF-8 编码
b'A\xc3\x84B\xc3\xa8C'
B.decode() # raw bytes 和 utf-8 不一致
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-35-084547e57fa4> in <module>()
----> 1 B.decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 1: invalid continuation byte
3.6 转换编码
我们总是可以把一个字符串转换为不同于源字符集默认的一种编码,但是,我们必须显式地提供一个编码名称以进行编码和解码:
S = 'AÄBèC'
S.encode()
b'A\xc3\x84B\xc3\xa8C'
T = S.encode('cp500') # 转换为 EBCDIC
T
b'\xc1c\xc2T\xc3'
U = T.decode('cp500') # 转换回 Unicode
U
'AÄBèC'
U.encode()
b'A\xc3\x84B\xc3\xa8C'
记住,只有当手动编写非 ASCII Unicode 的时候,才必须用到特殊的 Unicode 和十六进制字符转义。实际上,我们往往从文件载入这样的文本。Python 3 的文件对象(用 open 内置函数创建的)在读取文本字符串的时候自动地编码它们,并且在写入文本字符串的时候自动解码它们。因此,脚本往往可以广泛地处理字符串,而不必直接编码特殊字符。
3.7 源文件字符集编码声明
对于在脚本文件中编码的字符串,Python 默认地使用 UTF-8 编码,但是,它允许我们通过包含一个注释来指明想要的编码,从而将默认值修改为支持任意的字符集。这个注释必须拥有如下的形式,并且在 Python 中必须作为脚本的第一行或第二行出现:
# -*- coding: latin-1 -*-
当出现这种形式的注释时,Python 将自然按照给定的编码来识别表示的字符串。
4. 使用 Python 3.X bytes 对象
Python 3 bytes 对象是较小整数的一个序列,其中每个整数都在 0 到 255 之间,并且在显示的时候恰好打印为 ASCII 字符。
4.1 方法调用
bytes
对象是不可改变的,就像是 Python 中的 str
对象一样:
B = b'spam'
B.find(b'pa')
1
B.replace(b'pa', b'XY')
b'sXYm'
B.split(b'pa')
[b's', b'm']
B
b'spam'
B[0] = 'X'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-5-ccb8a8388b6d> in <module>()
----> 1 B[0] = 'X'
TypeError: 'bytes' object does not support item assignment
4.2 序列操作
如下的代码索引一个 bytes
对象并返回一个给出了该字节的二进制值的整数;bytes
实际上是 8 位整数的一个序列,但是,当作为整体显示的时候,为了方便起见,它打印为 ASCII 编码的字符的一个字符串。要查看一个给定的字节的值,使用 chr 内置函数来将其转换回字符:
B = b'spam'
B[0]
115
B[-1]
109
chr(B[0])
's'
list(B)
[115, 112, 97, 109]
B[1:]
b'pam'
B + b'lmn'
b'spamlmn'
B * 4
b'spamspamspamspam'
4.3 创建 bytes 对象的其他方式
可以用一个 str
和一个编码名来调用 bytes
构造函数,用一个可迭代的整数表示的字节值来调 用 bytes
构造函数,或者按照每个默认(或传入的)编码来编码一个 str
对象,从而创建 bytes
对象:
B = b'abc'
B
b'abc'
B = bytes('abc', 'ascii')
B
b'abc'
ord('a')
97
B = bytes([97, 98, 99])
B
b'abc'
B = 'spam'.encode()
B
b'spam'
S = B.decode()
S
'spam'
4.4 混合字符串类型
在 replace
调用中,我们必须传入两个 bytes
对象,str
对象在这里无效。Python 3 需要在某些环境下要求特殊的字符串类型并且如果需要的话期待手动转换:
B = b'spam'
B.replace('pa', 'XY')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-20-991fec2eae50> in <module>()
1 B = b'spam'
----> 2 B.replace('pa', 'XY')
TypeError: a bytes-like object is required, not 'str'
B.replace(b'pa', b'XY')
b'sXYm'
B = b'spam'
B.replace(bytes('pa'), bytes('xy'))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-22-e8c584a0eed6> in <module>()
1 B = b'spam'
----> 2 B.replace(bytes('pa'), bytes('xy'))
TypeError: string argument without an encoding
B.replace(bytes('pa', 'ascii'), bytes('xy', 'utf-8'))
b'sxym'
b'ab' + 'cd'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-3b2e43ca11ed> in <module>()
----> 1 b'ab' + 'cd'
TypeError: can't concat str to bytes
b'ab'.decode() + 'cd' # bytes to str
'abcd'
b'ab' + 'cd'.encode() # str to bytes
b'abcd'
b'ab' + bytes('cd', 'ascii') # str to bytes
b'abcd'
5. 使用 Python 3.X bytearray 对象
Python 3 还有第三个字符串类型 bytearray
,这是范围在 0 到 255 之间的整数的一个可变的序列,其本质是 bytes
的可变的变体。
5.1 bytearray 应用
bytearray
需要一个编码名称和字节字符串,因为文本和二进制字符串不能混合:
S = 'spam'
C = bytearray(S)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-28-fe7755f344cc> in <module>()
1 S = 'spam'
----> 2 C = bytearray(S)
TypeError: string argument without an encoding
C = bytearray(S, 'latin-1')
C
bytearray(b'spam')
B = b'spam'
C = bytearray(B)
C
bytearray(b'spam')
C[0]
115
# bytearray 可变,但必须赋值整数,而不是字符串
C[0] = 'x'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-33-41df5f19babf> in <module>()
1 # bytearray 可变,但必须赋值整数,而不是字符串
----> 2 C[0] = 'x'
TypeError: an integer is required
C[0] = b'x'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-34-f64daf7b4209> in <module>()
----> 1 C[0] = b'x'
TypeError: an integer is required
C[0] = ord('x')
C
bytearray(b'xpam')
C[1] = b'Y'[0]
C
bytearray(b'xYam')
处理 bytearray
对象借用了字符串和列表的方法:
C.append(b'LMN')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-37-856c5e4236d7> in <module>()
----> 1 C.append(b'LMN')
TypeError: an integer is required
C.append(ord('L'))
C
bytearray(b'xYamL')
C.extend(b'MNO')
C
bytearray(b'xYamLMNO')
C + b'!#'
bytearray(b'xYamLMNO!#')
C[1:]
bytearray(b'YamLMNO')
C.replace(b'xY', b'sp')
bytearray(b'spamLMNO')
C * 4
bytearray(b'xYamLMNOxYamLMNOxYamLMNOxYamLMNO')
5.2 Python 3.X 字符串类型总结
下面的例子展示了 bytes
和 bytearray
对象如何是 int
的序列,而 str
对象是字符的序列:
B = b'spam'
list(B)
[115, 112, 97, 109]
C
bytearray(b'xYamLMNO')
list(C)
[120, 89, 97, 109, 76, 77, 78, 79]
S = 'spam'
list(S)
['s', 'p', 'a', 'm']
- 对文本数据使用
str
; - 对二进制数据使用
bytes
; - 对想要原处修改的二进制数据使用
bytearray
。
6. 使用文本文件和二进制文件
- 文本模式文件 根据 Unicode 编码来解释文件内容,要么是平台的默认编码,要么是我们传递进的编码名。通过传递一个编码名来打开文件,我们可以强行进行 Unicode 文件的各种类型的转换。文本模型的文件也执行通用的行末转换:默认地,所有的行末形式映射为脚本中的一个单个的 '\n' 字符,而不管在什么平台上运行。文本文件也负责阅读和写入在某些 Unicode 编码方案中存储文件开始处的字节顺序标记(Byte Order Mark,BOM)。
- 二进制模式文件 不会返回原始的文件内容,而是作为表示字节值的整数的一个序列,没有编码或解码,也没有行末转换。
6.1 Python 3.X 中的文本和二进制模式
open('temp', 'w').write('abc\n') # 文本模式输出,提供一个 str
4
open('temp', 'r').read() # 文本模式输入,返回一个 str
'abc\n'
open('temp', 'rb').read() # 二进制模式输入,返回一个 bytes
b'abc\r\n'
在 Windows 上,文本模式的文件是在输出中把 \n
行末符号转换为 \r\n
的;在输入上,文本模式把 \r\n
转换回 \n
,但二进制模式不会这么做:
open('temp', 'wb').write(b'abc\n') # 二进制模式输出,提供一个 bytes
4
open('temp', 'r').read() # 文本模式输入,返回一个 str
'abc\n'
open('temp', 'rb').read() # 二进制模式输入,返回一个 bytes
b'abc\n'
在二进制模式输出中,\n
行末字符没有扩展为 \r\n
。
二进制模式文件总是作为一个 bytes
对象返回内容,但是接受一个 bytes
或 bytearray
对 象以供写入。
6.2 类型和内容错误匹配
如果试图向一个文本文件写入一个 bytes
或者向二进制文件写入一个 str
,将会得到错误:
open('temp', 'w').write('abc\n')
4
open('temp', 'w').write(b'abc\n')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-63aaba6b9d94> in <module>()
----> 1 open('temp', 'w').write(b'abc\n')
TypeError: write() argument must be str, not bytes
open('temp', 'wb').write(b'abc\n')
4
open('temp', 'wb').write('abc\n')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-10-c5d177cb0baf> in <module>()
----> 1 open('temp', 'wb').write('abc\n')
TypeError: a bytes-like object is required, not 'str'
文本模式的输入文件需要一个 str
而不是一个 bytes
用于内容,因此,在 Python 3 中,没有方法把真正的二进制数据写入一个文本模式文件中。文本模式输入文件必须能够针对每个 Unicode 编码来解码内容,因此,也没有办法在文本模式中读取真正的二进制数据。
7. 使用 Unicode 文件
Python 3 的 open
调用针对文本文件接受一个编码,在数据传输的时候,它自动为我们编码和解码。这允许我们处理用不同编码创建的 Unicode 文本,而不仅是平台默认编码的 Unicode 文本,并且以不同的编码存储以供转换。
7.1 在 Python 3.X 中读取和写入 Unicode
有两种办法可以把字符串转换为不同的编码:用方法调用手动地转换和在文件输入输出上自动地转换。
S = 'A\xc4B\xe8C' # 5 字符字符串,非 ASCII
S
'AÄBèC'
手动编码
L = S.encode('latin-1') # 5 字节
L
b'A\xc4B\xe8C'
U = S.encode('utf-8') # 7 字节
U
b'A\xc3\x84B\xc3\xa8C'
文件输出编码
要把字符串以特定编码写入一个文本文件,可以直接把想要的编码名称传递给 open:
open('latindata', 'w', encoding='latin-1').write(S)
5
open('utf8data', 'w', encoding='utf-8').write(S) # 以 utf-8 写入
5
open('latindata', 'rb').read()
b'A\xc4B\xe8C'
open('utf8data', 'rb').read()
b'A\xc3\x84B\xc3\xa8C'
文件输入编码
要读取任意的 Unicode 数据,可以直接把文件的编码类型名称传入 open
,并且,它自动根据 raw bytes解码出字符串;也可以手动地读取 raw bytes 并解码:
open('latindata', 'r', encoding='latin-1').read()
'AÄBèC'
open('utf8data', 'r', encoding='utf-8').read()
'AÄBèC'
X = open('latindata', 'rb').read()
X.decode('latin-1')
'AÄBèC'
X = open('utf8data', 'rb').read()
X.decode()
'AÄBèC'
7.2 Unicode 文件名和流
Python 也支持非 ASCII 文件名的概念。它们在 sys 中是独立的设置,每个 Python 版本和平台都可能不同:
import sys
sys.getdefaultencoding(), sys.getfilesystemencoding()
('utf-8', 'utf-8')
文件名:文本和 bytes
文件名编码通常不是问题。简而言之,对于以 Unicode 文本字符串形式给出的文件名,open
调用将自动对底层平台的文件名约定进行编码。对文件工具传入作为字节字符串的任意预编码文件名会覆盖自动编码,并强制文件名结果返回编码字节字符串形式,如果文件名根据底层平台的约定是不可解码的,那么这是非常有用的:
f = open('xxx¥', 'w', encoding='utf-8')
f.write('\xA5999\n')
f.close()
print(open('xxx¥', encoding='utf-8').read())
¥999
# print(open(b'xxx\xA5').read())
流内容:PYTHONIOENCODING
环境变量 PYTHONIOENCODING 可以用于设置标准流(输入、输出和错误)中的文本的编码。这个设置会重载 Python 默认的打印文本的编码,有时需要将其设置为通用 Unicode 格式(如 UTF-8)来打印非 ASCII 文本,并在 shell 窗口中显示此类文本。