第四章 文本和字节序列

本文详细介绍了Python 3中Unicode字符串、字节序列及其编码解码过程,强调了理解Unicode和字节序列差异的重要性。讨论了Unicode字符、码位、字节表示以及UnicodeError的处理方法,还涵盖了处理文本文件的最佳实践和默认编码陷阱。内容包括字符问题、字节序列特性、编码器/解码器、错误处理、BOM以及处理文本文件时的Unicode三明治原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Humans use text. Computers speak bytes.

                ----Esther Nam and Travis Fischer, Character Encoding and Unicode in Python

Python3明确区分了人类可读的文本字符串和原始序列字符串。隐式地把字节序列转换成Unicode文本已成过去。本章将讨论Unicode字符串、二进制序列以及二者之间转换时使用的编码。

根据您的 Python 编程场景,深入理解Unicode对您来说可能重要也可能不重要。最后,本章涉及的大多数问题对只处理 ASCII 文本的程序员没有影响。但是即便如此,也不能避而不谈字符串和字节序列的区别。此外,你会发现专门的二进制序列类型所提供的功能,是Python2中全功能的str类型所不具有的。

本章将讨论以下话题:

  • 字符、码位和字节表述
  • 二进制序列的独特功能:bytesbytearray,和 memoryview
  • 完整 Unicode 和旧字符集的编解码器
  • 避免和处理编码错误
  • 处理文本文件的最佳实践
  • 默认编码的陷阱和标准I/O问题
  • 标准化的进行安全的 Unicode 文本比较
  • 用于标准化、大小写折叠和强力移除音调符号的实用函数
  • 使用locale模块和PyUCA库正确的排序Uncode文本
  • Unicode 数据库中的字符元数据
  • 能够处理str和bytes的双模式API
  • 从字符组合构建表情符号

本章新增的内容

Python 3 中对 Unicode 的支持已经全面稳定了一段时间,所以本章最大的变化是新的表情符号部分——不是因为 Python 的变化,而是因为表情符号和表情符号组合的日益流行。2020 年发布的 Unicode 13 支持 3000 多个表情符号,其中许多是通过组合 Unicode 字符构建的,详见“Multi-character emojis”

第二版中的另一个新内容是“按名称查找字符”,包括用于搜索 Unicode 数据库的源代码,这是从命令行查找带圆圈数字和微笑的猫的好方法。

值得一提的一个小变化是 Windows 上的 Unicode 支持,它比 Python 3.6 更好、更简单,我们将在“注意编码默认值”中看到。

字符问题

“字符串”的概念很简单:一个字符串就是一个字符序列。问题在于”字符“的定义。

2021 年,我们对“字符”的最佳定义是 Unicode 字符。因此,您从 Python 3 str 中获得的元素是 Unicode 字符,就像 Python 2 中的 unicode 对象的元素一样——而不是您从 Python 2 str 中获得的原始字节。

Unicode 标准明确地将字符的标识与特定的字节表述进行区分:


  • 字符的标识——即code point(码位)——是一个从 0 到 1,114,111(十进制)的数字,在 Unicode 标准中显示为 4 到 6 个带有“U+”前缀的十六进制数字,从 U+0000 到 U+10FFFF。例如,字母 A 的码位是 U+0041,欧元符号是 U+20AC,音乐符号 G 谱号码位为 U+1D11E。大约 13% 的有效码位在 Unicode 13.0.0(Python 3.9 中使用的标准)中分配了字符。
  • 代表字符的实际字节取决于所用的编码。编码是一种将码位转换为字节序列的算法,解码是将字节序列转换为码位的算法。字母 A (U+0041) 的码位在 UTF-8 编码中编码为单个字节 \x41,而在 UTF-16LE 编码中编码为两个字节 \x41\x00。再举一个例子,UTF-8 需要三个字节——\xe2\x82\xac——来编码欧元符号 (U+20AC),但在 UTF-16LE 中,相同的码位被编码为两个字节:\xac\x20。

从码位转换为字节是编码;从字节到码位的转换就是解码。请参见下面示例。

>>> s = 'café'
>>> len(s)  
4
>>> b = s.encode('utf8')  
>>> b
b'caf\xc3\xa9'  
>>> len(b)  
5
>>> b.decode('utf8')  
'café'

字节概要

新的二进制序列类型在很多方面和Python2的str类型不同。首先要知道的是,二进制序列有两种基本的内置类型:Python 3 中引入的不可变的bytes类型和 Python 2.6 中添加的可变的bytearray类型。

bytes 或 bytearray对象中的每一个元素都是 0 到 255 之间的整数,而不是像 Python 2 str对象那样是单个的字符。然而,二进制序列的切片总是产生相同类型的二进制序列——包括长度为 1 的切片,如下例所示:

>>> cafe = bytes('café', encoding='utf_8')  
>>> cafe
b'caf\xc3\xa9'
>>> cafe[0]  
99
>>> cafe[:1]  
b'c'
>>> cafe_arr = bytearray(cafe)
>>> cafe_arr  
bytearray(b'caf\xc3\xa9')
>>> cafe_arr[-1:]  
bytearray(b'\xa9')

注意:my_bytes[0] 获取了一个 int 但 my_bytes[:1] 返回一个长度为 1 的 bytes 对象的事实可能令人惊讶,并使得处理二进制数据的程序难以同时支持 Python 2.7 和 Python 3。但它与许多其他语言以及其他 Python 序列类型一致——除了 str这个序列类型,它是唯一一个 s[0] == s[:1] 的序列类型。

虽然二进制序列实际上是整数序列,但它们的字面量表示表明其中有ASCII 文本。因此,每个字节的值有以下四种方式进行展示:

  • 对于可打印 ASCII 范围内的字节(从空格到 ~),使用 ASCII 字符本身。
  • 对于与制表符、换行符、回车符和 \ 对应的字节,使用转义序列 \t、\n、\r 和 \\。
  • 如果字符串分隔符 ' 和 " 都出现在字节序列中,则整个序列由 ' 分隔,其中的任何 ' 都转义为 \'。
  • 对于其他字节值,使用十六进制转义序列(例如,\x00 是空字节)。

这就是为什么我们在上例看到的是b'caf\xc3\xa9',前三个字节b‘caf’在可打印的ASCII范围内,而后两个字节则不然。

除了格式化(format、format_map)和其他一些依赖 Unicode 数据之外的方法,包括 casefold、isdecimal、isidentifier、isnumeric、isprintable 和 encode,bytes和bytearray类型支持str类型的其他所有方法。这意味着您可以使用熟悉的字符串方法,如 endwith、replace、strip、translate、upper 和许多其他二进制序列——只使用bytes而不是 str 参数。此外,如果正则表达式是从二进制序列而不是 str 编译的,re 模块中的正则表达式函数也能处理于二进制序列。从 Python 3.5 开始,% 运算符可以再次处理二进制序列。

二进制序列有一个 str 没有的类方法,名为 fromhex,它通过解析可选用空格分隔的十六进制数字对来构建二进制序列: 

>>> bytes.fromhex('31 4B CE A9')
b'1K\xce\xa9'

构建bytes或bytearray实例的其他方法是调用它们的构造函数:

  • 一个 str对象 和encoding关键字参数
  • 一个可迭代对象,对象中的每个元素为0-255之间的数值
  • 一个实现缓存协议的对象(例如., bytesbytearraymemoryvi
<think>我们正在讨论Python中的字节序列(bytes)字符串(str)的关系,以及字节序列中0-255的含义'\x'前缀的含义。 用户的问题:我想理解Python中字节序列与字符串的关系以及'\x'在字节序列中的含义。请问Python中字节序列0-255的含义以及'\x'代表什么? 根据之前的回答,我们知道: - 字节字符串(bytes)是一个由0到255之间的整数组成的序列,表示原始字节数据。 - 普通字符串(str)是Unicode字符序列。 现在,用户特别询问了字节序列中0-255的含义以及'\x'的含义。 解释如下: 1. 字节序列中的每个元素是一个整数,范围在0到255(包括0255)。这是因为一个字节(byte)由8位(bits)组成,可以表示2^8=256个不同的值,即0到255。 2. 在Python中,当我们用`b'...'`表示一个字节字符串时,对于字节值在32到126之间的可打印ASCII字符,会直接显示为对应的字符(例如,65显示为'A'),而对于不可打印的字符(如0到31以及127到255),则使用转义序列表示。其中,最常用的转义序列就是十六进制转义,形式为`\xHH`,其中HH是两个十六进制数字。 3. `\x`是十六进制转义序列的前缀,表示后面两个字符是十六进制数字,用于表示一个字节。例如: - `\x41` 表示十六进制数0x41,即十进制的65,也就是大写字母'A'的ASCII码。 - `\x00` 表示十六进制数0x00,即十进制的0。 4. 为什么使用十六进制?因为一个字节正好可以用两个十六进制数字表示(00到FF),这样表示非常紧凑且直观。 5. 字节字符串中,如果一个字节的值恰好对应可打印的ASCII字符,那么Python会尽量显示该字符,否则就显示为`\x`加两个十六进制数字。例如: - `b'A'` 显示为 `b'A'`,但实际上它存储的是整数65。 - 字节0无法打印,所以显示为`\x00`。 6. 注意:在字节字符串中,反斜杠`\`本身是一个转义字符,所以当我们需要表示一个反斜杠时,必须用`\\`。但是,在`\x`中,反斜杠后面的x组合表示这是一个十六进制转义序列。 7. 示例: - 创建一个包含三个字节的字节串:第一个字节是0(即`\x00`),第二个字节是65(即`\x41`,但显示为'A',因为65是可打印的),第三个字节是255(即`\xff`)。 ```python b = b'\x00A\xff' print(b) # 输出:b'\x00A\xff' ``` 这里,中间的65被显示为'A',而0255分别显示为`\x00``\xff`。 8. 字节序列与字符串的关系: - 字符串是文本,而字节序列是二进制数据。 - 字符串可以通过编码(如UTF-8)转换为字节序列字节序列可以通过解码转换为字符串。 - 例如: ```python s = "你好" b = s.encode('utf-8') # 得到字节序列,可能是b'\xe4\xbd\xa0\xe5\xa5\xbd' s2 = b.decode('utf-8') # 得到原字符串 ``` 9. 为什么需要字节序列? - 计算机存储网络传输都是以字节为单位。 - 文本数据在存储或传输前必须编码为字节序列。 10. 注意:在Python中,字节序列的类型是`bytes`,它是不可变的。如果需要可变的字节序列,可以使用`bytearray`。 11. 关于用户提到的`\u`:在字符串中,`\u`表示Unicode转义序列(后面跟4个十六进制数字),例如`\u4f60`表示汉字“你”。但在字节字符串中,`\u`不会被特殊解释,因为字节字符串只处理字节,不处理Unicode字符。实际上,在字节字符串中,`\u`会被视为两个单独的字节:反斜杠(92)字母u(117),然后按照它们的ASCII码存储。 因此,在字节字符串中看到`\u`,实际上它代表的是6个字节:反斜杠、字母u以及后面的四个十六进制数字(每个字符一个字节)。而如果是在普通字符串中,`\u`会被解释为一个Unicode字符。 总结: - 字节序列中的每个整数(0-255)表示一个字节的数值。 - `\x`是字节字符串中用于表示非打印字符的十六进制转义序列前缀。 - 字节序列与字符串的转换通过编码解码完成。 根据用户的问题,我们重点解释了字节序列0-255的含义`\x`的含义。 生成相关问题: 1. 在Python中如何创建一个包含非ASCII字符的字节序列? 2. 字节序列字符串在性能上有何差异? 3. 如何将字节序列转换为十六进制字符串表示? 4. 为什么在字节字符串中,有些字符显示为十六进制转义而有些直接显示字符? 5. 在Python中处理中文字符的字节序列需要注意什么?</think>在Python中,字节序列(`bytes`类型)与普通字符串(`str`类型)有本质区别,而`\x`前缀在字节序列中具有特定含义。以下是详细解析: ### 一、字节序列中的0-255含义 字节序列是由0-255之间的整数组成的不可变序列,每个整数代表一个字节(8位)的原始二进制数据: ```python byte_data = b'\x48\x65\x6c\x6c\x6f' # 等价于 b'Hello' print(byte_data[0]) # 72 (十进制) print(hex(byte_data[0])) # 0x48 (十六进制) ``` - **0-127范围**:对应ASCII字符集(如`65->'A'`, `97->'a'`) - **128-255范围**:扩展ASCII字符(如`©`,`®`等特殊符号) - **本质**:每个元素是整数而非字符,例如: ```python type(b'A'[0]) # <class 'int'> 值为65 ``` ### 二、`\x`前缀的含义 `\x`是Python中表示**十六进制字节值**的转义序列: 1. **语法结构**:`\xHH` - `H`代表十六进制数字(0-9, A-F) - 例如:`\x41`表示十六进制数0x41(即十进制65) 2. **使用场景**: ```python # 表示不可打印字符 null_byte = b'\x00' # 空字节 del_byte = b'\x7f' # DEL控制符 # 表示非ASCII字符 euro_sign = b'\xe2\x82\xac' # €符号的UTF-8编码 ``` ### 三、字节序列与字符串的关系 | 特性 | 字节序列 (`bytes`) | 字符串 (`str`) | |------------|--------------------------|-------------------------| | **前缀** | `b'...'` | `'...'` | | **内容** | 原始字节 (0-255整数) | Unicode字符 | | **编码** | 无编码概念 | UTF-8等文本编码 | | **转换** | `.decode('utf-8')` | `.encode('utf-8')` | | **示例** | `b'\xc3\xa9'` (é的UTF-8) | `'é'` | ```python # 转换示例 text = "é" # Unicode字符串 byte_data = text.encode('utf-8') # b'\xc3\xa9' new_text = byte_data.decode('utf-8') # "é" ``` ### 四、为什么需要字节序列? 1. **二进制数据处理**: - 网络通信(TCP/UDP数据包) - 文件读写(如图片/音频) ```python with open('image.png', 'rb') as f: raw_bytes = f.read() # 读取为字节序列 ``` 2. **硬件交互**: ```python # 模拟MAC地址处理 mac_bytes = b'\x00\x0c\xcf\x01\xff\xfe' print(':'.join(f'{b:02x}' for b in mac_bytes)) # 00:0c:cf:01:ff:fe ``` 3. **避免编码问题**: - 字节序列不涉及字符编码,直接操作原始数据 ### 五、`\u`与`\x`的区别 | 转义序列 | 含义 | 示例 | |----------|-----------------------|--------------------------| | `\xHH` | 单字节值(HH为十六进制) | `b'\x41'` → 65 (A的ASCII) | | `\uXXXX` | Unicode字符(字符串专用) | `'\u4f60'` → "你" | ```python # 错误用法:字节序列不支持\u byte_data = b'\u4f60' # 错误!实际存储6个字节:\、u、4、f、6、0 ``` ### 六、最佳实践建议 1. **文本处理**:始终使用`str`类型 2. **二进制数据**: - 使用`bytes`类型存储传输 - 用`.hex()`方法转为可读格式: ```python b'\x00\xff'.hex() # '00ff' bytes.fromhex('00ff') # 转回字节序列 ``` 3. **网络编程**: ```python # 构造ARP数据包 arp_packet = b'\xff\xff\xff\xff\xff\xff' + b'\x00\x0c\x29'*2 + b'\x08\x06' ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值