欢迎光临
我们一直在努力

《流畅的Python》读书笔记05: 第一部分 数据结构 - 文本和字节序列

作者: andylin02
学习章节: 第 4 章 文本和字节序列
关键词: Unicode|UTF-8|编码|解码|bytes|bytearray|memoryview|struct|UnicodeEncodeError|UnicodeDecodeError|Unicode三明治|unicodedata|normalize|casefold|locale.strxfrm|BOM|chardet

一、本章概述

人类使用文本,计算机使用字节序列。 —— Esther Nam 和 Travis Fischer

Python 3 明确区分了人类可读的文本字符串和原始的字节序列。隐式地把字节序列转换成 Unicode 文本已经成为过去。

本章的核心内容围绕三类问题展开:

  • Unicode 字符串:字符的标识(码位)及其具体表示的差异
  • 二进制序列:bytes 与 bytearray 的语义和使用
  • 编解码:在文本字符串与字节序列之间进行可靠、无歧义的转换

本章内容大纲

  • 字符、码位和字节表示的基本概念
  • bytes 和 bytearray 的特性
  • struct 处理打包字节和 memoryview 内存视图
  • 基本的编解码器及其用法
  • 编解码错误(UnicodeEncodeError、UnicodeDecodeError)处理
  • 处理文本文件的最佳实践:「Unicode 三明治」
  • Unicode 规范化与安全比较
  • Unicode 文本排序
  • 支持字符串和字节序列的双模式 API

本章核心概念架构图

#mermaid-svg-iby3pTjKtjEmQtCN{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iby3pTjKtjEmQtCN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iby3pTjKtjEmQtCN .error-icon{fill:#552222;}#mermaid-svg-iby3pTjKtjEmQtCN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iby3pTjKtjEmQtCN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iby3pTjKtjEmQtCN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iby3pTjKtjEmQtCN .marker.cross{stroke:#333333;}#mermaid-svg-iby3pTjKtjEmQtCN svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iby3pTjKtjEmQtCN p{margin:0;}#mermaid-svg-iby3pTjKtjEmQtCN .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-iby3pTjKtjEmQtCN .cluster-label text{fill:#333;}#mermaid-svg-iby3pTjKtjEmQtCN .cluster-label span{color:#333;}#mermaid-svg-iby3pTjKtjEmQtCN .cluster-label span p{background-color:transparent;}#mermaid-svg-iby3pTjKtjEmQtCN .label text,#mermaid-svg-iby3pTjKtjEmQtCN span{fill:#333;color:#333;}#mermaid-svg-iby3pTjKtjEmQtCN .node rect,#mermaid-svg-iby3pTjKtjEmQtCN .node circle,#mermaid-svg-iby3pTjKtjEmQtCN .node ellipse,#mermaid-svg-iby3pTjKtjEmQtCN .node polygon,#mermaid-svg-iby3pTjKtjEmQtCN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iby3pTjKtjEmQtCN .rough-node .label text,#mermaid-svg-iby3pTjKtjEmQtCN .node .label text,#mermaid-svg-iby3pTjKtjEmQtCN .image-shape .label,#mermaid-svg-iby3pTjKtjEmQtCN .icon-shape .label{text-anchor:middle;}#mermaid-svg-iby3pTjKtjEmQtCN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iby3pTjKtjEmQtCN .rough-node .label,#mermaid-svg-iby3pTjKtjEmQtCN .node .label,#mermaid-svg-iby3pTjKtjEmQtCN .image-shape .label,#mermaid-svg-iby3pTjKtjEmQtCN .icon-shape .label{text-align:center;}#mermaid-svg-iby3pTjKtjEmQtCN .node.clickable{cursor:pointer;}#mermaid-svg-iby3pTjKtjEmQtCN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iby3pTjKtjEmQtCN .arrowheadPath{fill:#333333;}#mermaid-svg-iby3pTjKtjEmQtCN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iby3pTjKtjEmQtCN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iby3pTjKtjEmQtCN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iby3pTjKtjEmQtCN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iby3pTjKtjEmQtCN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iby3pTjKtjEmQtCN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iby3pTjKtjEmQtCN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iby3pTjKtjEmQtCN .cluster text{fill:#333;}#mermaid-svg-iby3pTjKtjEmQtCN .cluster span{color:#333;}#mermaid-svg-iby3pTjKtjEmQtCN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iby3pTjKtjEmQtCN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iby3pTjKtjEmQtCN rect.text{fill:none;stroke-width:0;}#mermaid-svg-iby3pTjKtjEmQtCN .icon-shape,#mermaid-svg-iby3pTjKtjEmQtCN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iby3pTjKtjEmQtCN .icon-shape p,#mermaid-svg-iby3pTjKtjEmQtCN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iby3pTjKtjEmQtCN .icon-shape .label rect,#mermaid-svg-iby3pTjKtjEmQtCN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iby3pTjKtjEmQtCN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iby3pTjKtjEmQtCN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iby3pTjKtjEmQtCN :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

编解码转换

计算机视角

人类视角

编码

解码

字符串 (str)Unicode 文本

字节序列 (bytes/bytearray)原始字节数据

编码 (encode)str → bytes

解码 (decode)bytes → str

UnicodeEncodeError目标编码不支持字符

UnicodeDecodeError字节序列对不上解码器

二、字符与编码基础

在 Unicode 标准中,每个字符都有两个层面的定义:

  • 字符的标识——码位(code point) ,在 Unicode 标准中用 4-6 个十六进制数字表示,前缀为 “U+”。字符 ‘A’ 的码位是 U+0041,高音谱号是 U+1D11E。
  • 字符的具体表述 取决于所用的编码。编码是在码位与字节序列之间转换时使用的算法。例如字符 ‘A’(U+0041),在 UTF-8 编码中编码为字节 \\x41,在 UTF-16LE 编码中编码为字节 \\x41\\x00。
  • 在 Python 3 中,str 类型相当于 Python 2 中的 unicode 类型,内存中以码位对应二进制序列存储,而没有进行编码。

    把码位转换成字节序列的过程是编码(encoding);把字节序列转换成码位的过程是解码(decoding)。

    基本的编码和解码操作如下:

    # 编码(encoding):从字符串到字节序列
    s = 'café'
    print(f'字符串长度(字符数): {len(s)}') # 4 个字符

    # 使用 UTF-8 编码
    b = s.encode('utf-8')
    print(f'编码后的字节序列: {b}') # b'caf\\xc3\\xa9'
    print(f'字节序列长度: {len(b)}') # 5 个字节('é' 在 UTF-8 中占用 2 个字节)

    # 解码(decoding):从字节序列到字符串
    c = b.decode('utf-8')
    print(f'解码后的字符串: {c}') # 'café'

    编码/解码流程图

    #mermaid-svg-McDGxn255AX7mJ3k{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-McDGxn255AX7mJ3k .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-McDGxn255AX7mJ3k .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-McDGxn255AX7mJ3k .error-icon{fill:#552222;}#mermaid-svg-McDGxn255AX7mJ3k .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-McDGxn255AX7mJ3k .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-McDGxn255AX7mJ3k .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-McDGxn255AX7mJ3k .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-McDGxn255AX7mJ3k .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-McDGxn255AX7mJ3k .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-McDGxn255AX7mJ3k .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-McDGxn255AX7mJ3k .marker{fill:#333333;stroke:#333333;}#mermaid-svg-McDGxn255AX7mJ3k .marker.cross{stroke:#333333;}#mermaid-svg-McDGxn255AX7mJ3k svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-McDGxn255AX7mJ3k p{margin:0;}#mermaid-svg-McDGxn255AX7mJ3k .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-McDGxn255AX7mJ3k .cluster-label text{fill:#333;}#mermaid-svg-McDGxn255AX7mJ3k .cluster-label span{color:#333;}#mermaid-svg-McDGxn255AX7mJ3k .cluster-label span p{background-color:transparent;}#mermaid-svg-McDGxn255AX7mJ3k .label text,#mermaid-svg-McDGxn255AX7mJ3k span{fill:#333;color:#333;}#mermaid-svg-McDGxn255AX7mJ3k .node rect,#mermaid-svg-McDGxn255AX7mJ3k .node circle,#mermaid-svg-McDGxn255AX7mJ3k .node ellipse,#mermaid-svg-McDGxn255AX7mJ3k .node polygon,#mermaid-svg-McDGxn255AX7mJ3k .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-McDGxn255AX7mJ3k .rough-node .label text,#mermaid-svg-McDGxn255AX7mJ3k .node .label text,#mermaid-svg-McDGxn255AX7mJ3k .image-shape .label,#mermaid-svg-McDGxn255AX7mJ3k .icon-shape .label{text-anchor:middle;}#mermaid-svg-McDGxn255AX7mJ3k .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-McDGxn255AX7mJ3k .rough-node .label,#mermaid-svg-McDGxn255AX7mJ3k .node .label,#mermaid-svg-McDGxn255AX7mJ3k .image-shape .label,#mermaid-svg-McDGxn255AX7mJ3k .icon-shape .label{text-align:center;}#mermaid-svg-McDGxn255AX7mJ3k .node.clickable{cursor:pointer;}#mermaid-svg-McDGxn255AX7mJ3k .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-McDGxn255AX7mJ3k .arrowheadPath{fill:#333333;}#mermaid-svg-McDGxn255AX7mJ3k .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-McDGxn255AX7mJ3k .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-McDGxn255AX7mJ3k .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-McDGxn255AX7mJ3k .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-McDGxn255AX7mJ3k .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-McDGxn255AX7mJ3k .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-McDGxn255AX7mJ3k .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-McDGxn255AX7mJ3k .cluster text{fill:#333;}#mermaid-svg-McDGxn255AX7mJ3k .cluster span{color:#333;}#mermaid-svg-McDGxn255AX7mJ3k div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-McDGxn255AX7mJ3k .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-McDGxn255AX7mJ3k rect.text{fill:none;stroke-width:0;}#mermaid-svg-McDGxn255AX7mJ3k .icon-shape,#mermaid-svg-McDGxn255AX7mJ3k .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-McDGxn255AX7mJ3k .icon-shape p,#mermaid-svg-McDGxn255AX7mJ3k .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-McDGxn255AX7mJ3k .icon-shape .label rect,#mermaid-svg-McDGxn255AX7mJ3k .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-McDGxn255AX7mJ3k .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-McDGxn255AX7mJ3k .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-McDGxn255AX7mJ3k :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    内存中的表示

    存储/传输

    bytes 对象原始字节数据可在网络/磁盘间传递

    解码过程

    b.decode('utf-8')

    根据编码规则将字节还原为码位序列

    结果: 'café'

    编码过程

    s.encode('utf-8')

    根据编码规则将码位映射为字节序列

    结果: b'caf\\xc3\\xa9'

    str 对象'café'码位: U+0063, U+0061, U+0066, U+00E9

    从上图可以看出,str 对象始终存在于内存中,bytes 对象则是存储或传输的形式。理解和记忆这「两条腿走路」的抽象,对于避免编解码错误至关重要。

    三、二进制序列:bytes 与 bytearray

    Python 内置了两种基本的二进制序列类型:不可变的 bytes 和可变的 bytearray。

    bytes 或 bytearray 对象的各个元素是 0-255(含)之间的整数。这一点和字符串截然不同:字符串的元素是长度为 1 的字符串,而二进制序列的元素是整数。

    # 创建 bytes 对象
    cafe = bytes('café', encoding='utf_8')
    print(cafe) # b'caf\\xc3\\xa9'
    print(cafe[0]) # 99(c 的 ASCII 码)
    print(cafe[:1]) # b'c'(切片仍然是 bytes 对象,哪怕是长度为 1 的切片)

    # 创建 bytearray 对象(可变)
    cafe_arr = bytearray(cafe)
    print(cafe_arr) # bytearray(b'caf\\xc3\\xa9')
    print(cafe_arr[1:]) # bytearray(b'\\x89')

    bytearray 没有字面量语法,显示时以 bytearray() 和字节序列字面量作为参数的形式呈现。

    bytes 对象的显示规则

    在打印或显示 bytes 对象时,Python 对不同的字节采用不同的显示方式:

    • ASCII 范围内的字节:使用 ASCII 字符本身(如 ‘c’, ‘a’, ‘f’)
    • 转义字符:制表符 \\t、换行符 \\n、回车符 \\r 和反斜杠 \\ 使用转义序列
    • 其他字节的值:使用十六进制转义序列(如 \\xc3, \\xa9)

    bytes vs bytearray 对比

    #mermaid-svg-XRpjEPv5qcvKaIBc{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XRpjEPv5qcvKaIBc .error-icon{fill:#552222;}#mermaid-svg-XRpjEPv5qcvKaIBc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XRpjEPv5qcvKaIBc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XRpjEPv5qcvKaIBc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XRpjEPv5qcvKaIBc .marker.cross{stroke:#333333;}#mermaid-svg-XRpjEPv5qcvKaIBc svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XRpjEPv5qcvKaIBc p{margin:0;}#mermaid-svg-XRpjEPv5qcvKaIBc .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster-label text{fill:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster-label span{color:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster-label span p{background-color:transparent;}#mermaid-svg-XRpjEPv5qcvKaIBc .label text,#mermaid-svg-XRpjEPv5qcvKaIBc span{fill:#333;color:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc .node rect,#mermaid-svg-XRpjEPv5qcvKaIBc .node circle,#mermaid-svg-XRpjEPv5qcvKaIBc .node ellipse,#mermaid-svg-XRpjEPv5qcvKaIBc .node polygon,#mermaid-svg-XRpjEPv5qcvKaIBc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XRpjEPv5qcvKaIBc .rough-node .label text,#mermaid-svg-XRpjEPv5qcvKaIBc .node .label text,#mermaid-svg-XRpjEPv5qcvKaIBc .image-shape .label,#mermaid-svg-XRpjEPv5qcvKaIBc .icon-shape .label{text-anchor:middle;}#mermaid-svg-XRpjEPv5qcvKaIBc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XRpjEPv5qcvKaIBc .rough-node .label,#mermaid-svg-XRpjEPv5qcvKaIBc .node .label,#mermaid-svg-XRpjEPv5qcvKaIBc .image-shape .label,#mermaid-svg-XRpjEPv5qcvKaIBc .icon-shape .label{text-align:center;}#mermaid-svg-XRpjEPv5qcvKaIBc .node.clickable{cursor:pointer;}#mermaid-svg-XRpjEPv5qcvKaIBc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XRpjEPv5qcvKaIBc .arrowheadPath{fill:#333333;}#mermaid-svg-XRpjEPv5qcvKaIBc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XRpjEPv5qcvKaIBc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XRpjEPv5qcvKaIBc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XRpjEPv5qcvKaIBc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XRpjEPv5qcvKaIBc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XRpjEPv5qcvKaIBc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster text{fill:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc .cluster span{color:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XRpjEPv5qcvKaIBc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XRpjEPv5qcvKaIBc rect.text{fill:none;stroke-width:0;}#mermaid-svg-XRpjEPv5qcvKaIBc .icon-shape,#mermaid-svg-XRpjEPv5qcvKaIBc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XRpjEPv5qcvKaIBc .icon-shape p,#mermaid-svg-XRpjEPv5qcvKaIBc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XRpjEPv5qcvKaIBc .icon-shape .label rect,#mermaid-svg-XRpjEPv5qcvKaIBc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XRpjEPv5qcvKaIBc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XRpjEPv5qcvKaIBc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XRpjEPv5qcvKaIBc :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    bytes vs bytearray 对比

    bytes────────不可变字面量语法: b'…'元素: 整数 0-255切片返回 bytes

    bytearray────────可变无字面量语法元素: 整数 0-255切片返回 bytearray

    四、更高级的二进制序列操作

    4.1 memoryview:零复制访问内存

    标准库里有一种内置类型 memoryview,它允许在不复制字节的情况下访问二进制数据,从而可以共享内存。memoryview 本身不能创建或存储字节序列,但它为其他二进制对象提供了共享内存访问视图,这在处理大型二进制数据时可以显著提升性能。

    # 创建 memoryview 并修改底层数据
    import array

    # 创建一个数组
    numbers = array.array('h', [2, 1, 0, 1, 2])
    print(f"原始数组: {numbers}")

    # 创建内存视图
    memv = memoryview(numbers)
    print(f"内存视图长度: {len(memv)}")
    print(f"内存视图内容: {memv.tolist()}") # [-2, -1, 0, 1, 2]

    # 转换为无符号字节视图
    memv_oct = memv.cast('B')
    print(f"字节视图内容: {memv_oct.tolist()}")

    # 通过内存视图修改数据(影响原始数据)
    memv_oct[5] = 4 # 修改第 6 个字节为 4
    print(f"修改后的数组: {numbers}")

    4.2 struct:解析打包的二进制数据

    struct 模块可以将打包的字节解析为不同类型字段的元组,也可以执行相反的操作。struct 与 bytes、bytearray 和 memoryview 对象配合使用,尤其适合处理二进制文件格式和网络协议。

    import struct
    import binascii

    # 打包(pack):将 Python 值转换为字节序列
    fmt = '<3s3sHH' # 小端字节序,两个3字节字符串,两个16位整数
    packed = struct.pack(fmt, b'GIF', b'89a', 100, 200)
    print(f"打包后的字节序列: {binascii.hexlify(packed)}")

    # 解包(unpack):从字节序列提取字段值
    unpacked = struct.unpack(fmt, packed)
    print(f"解包后的元组: {unpacked}") # (b'GIF', b'89a', 100, 200)

    # 更复杂的示例:使用 memoryview 与 struct 结合
    data = b'\\x01\\x02\\x03\\x04\\x05\\x06'
    fmt = '>HHB' # 大端序,两个无符号短整数,一个无符号字符
    result = struct.unpack(fmt, data[:5])
    print(f"部分数据解包: {result}")

    五、基本编解码器

    Python 发行版中包含了超过 100 种编解码器,用于在文本和字节之间相互转换。表中列出了一些最为常用的编解码器:

    编解码器别名说明
    utf-8 utf8, U8 最常用,兼容 ASCII,能表示所有 Unicode 字符
    latin1 iso8859_1 作为其他编码基础的编码,例如 cp1252 和 Unicode
    cp1252 windows-1252 Windows 系统默认的西欧编码
    gb2312 gbk 编码简体中文的陈旧标准,亚洲语言中应用最广泛的多字节编码之一
    utf-16le UTF-16 16 位编码方案的一种形式

    下面的代码演示了基本编解码器的使用:

    # 基本编码示例
    content = 'São Paulo'
    for codec in ['utf_8', 'utf_16']:
    print(f"{codec}: {content.encode(codec)}")
    # utf_8: b'S\\xc3\\xa3o Paulo'
    # utf_16: b'\\xff\\xfeS\\x00\\xe3\\x00o\\x00 \\x00P\\x00a\\x00u\\x00l\\x00o\\x00'

    关于编码器和别名:encode() 的 encoding 参数不仅接受包含下划线的官方名称 utf_8,也接受带连字符 utf-8 和全大写别名 U8。

    六、处理编解码问题

    6.1 异常类型

    遇到编码/解码相关的问题时,首先要区分异常类型:

    异常类型发生条件场景说明
    UnicodeEncodeError 将 str 转换为字节序列时 字符串中包含目标编码不支持的字符
    UnicodeDecodeError 将字节序列读取为 str 时 字节序列不符合预期的编码格式
    SyntaxError 在源码中使用非 ASCII 字符且未声明编码时 Python 源文件默认编码为 UTF-8,但某些环境可能使用其他编码

    6.2 处理 UnicodeEncodeError

    content = 'São Paulo'

    try:
    # cp437 编码不支持 'ã' 字符
    content.encode('cp437')
    except UnicodeEncodeError as e:
    print(f"编码错误: {e}")
    # 'charmap' codec can't encode character '\\xe3' in position 1: character maps to <undefined>

    # 错误处理策略
    print(content.encode('cp437', errors='ignore')) # b'So Paulo'
    print(content.encode('cp437', errors='replace')) # b'S?o Paulo'
    print(content.encode('cp437', errors='xmlcharrefreplace')) # b'São Paulo'

    errors 参数提供的几种常见策略:

    • 'ignore':跳过无法编码的字符(会丢失信息)
    • 'replace':用 ? 替换无法编码的字符
    • 'xmlcharrefreplace':将无法编码的字符替换为 XML 实体(如 &#227;)

    6.3 处理 UnicodeDecodeError

    octets = b'Montr\\xe9al'

    # 尝试不同的编码解码
    print(octets.decode('cp1252')) # Montréal(正确)
    print(octets.decode('iso8859_7')) # Montrιal(错误)
    print(octets.decode('koi8_r')) # MontrИal(错误)

    try:
    print(octets.decode('utf-8'))
    except UnicodeDecodeError as e:
    print(f"解码错误: {e}") # 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte

    # 使用 replace 策略
    print(octets.decode('utf-8', errors='replace')) # Montr�al

    无法确定字节序列的编码时,可以使用第三方库 chardet 进行编码检测。chardet 能够检测 ASCII、GBK、UTF-8、日文等多种编码。

    6.4 BOM(字节序标记)

    UTF-16 编码的字节序列开头会有一个 \\ufffe 标记,用于表示字节序:

    • 大端序(Big-endian):\\xfe\\xff
    • 小端序(Little-endian):\\xff\\xfe

    UTF-8 编码不需要 BOM,但微软在 UTF-8 文件中也加入了 BOM(\\xef\\xbb\\xbf)并因此而可能引发兼容性问题。

    七、处理文本文件:Unicode 三明治

    处理文本的最佳实践是「Unicode 三明治」:

  • 尽早解码:在输入时(例如读取文件),尽早把字节序列解码成字符串
  • 中间纯文本:业务逻辑中只处理 str 对象,不应该在中间环节编码或解码
  • 尽可能晚地编码:在输出时,尽可能晚地将字符串编码成字节序列
  • #mermaid-svg-Oa1yRh73uc2RUc3u{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Oa1yRh73uc2RUc3u .error-icon{fill:#552222;}#mermaid-svg-Oa1yRh73uc2RUc3u .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Oa1yRh73uc2RUc3u .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Oa1yRh73uc2RUc3u .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Oa1yRh73uc2RUc3u .marker.cross{stroke:#333333;}#mermaid-svg-Oa1yRh73uc2RUc3u svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Oa1yRh73uc2RUc3u p{margin:0;}#mermaid-svg-Oa1yRh73uc2RUc3u .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster-label text{fill:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster-label span{color:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster-label span p{background-color:transparent;}#mermaid-svg-Oa1yRh73uc2RUc3u .label text,#mermaid-svg-Oa1yRh73uc2RUc3u span{fill:#333;color:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u .node rect,#mermaid-svg-Oa1yRh73uc2RUc3u .node circle,#mermaid-svg-Oa1yRh73uc2RUc3u .node ellipse,#mermaid-svg-Oa1yRh73uc2RUc3u .node polygon,#mermaid-svg-Oa1yRh73uc2RUc3u .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Oa1yRh73uc2RUc3u .rough-node .label text,#mermaid-svg-Oa1yRh73uc2RUc3u .node .label text,#mermaid-svg-Oa1yRh73uc2RUc3u .image-shape .label,#mermaid-svg-Oa1yRh73uc2RUc3u .icon-shape .label{text-anchor:middle;}#mermaid-svg-Oa1yRh73uc2RUc3u .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Oa1yRh73uc2RUc3u .rough-node .label,#mermaid-svg-Oa1yRh73uc2RUc3u .node .label,#mermaid-svg-Oa1yRh73uc2RUc3u .image-shape .label,#mermaid-svg-Oa1yRh73uc2RUc3u .icon-shape .label{text-align:center;}#mermaid-svg-Oa1yRh73uc2RUc3u .node.clickable{cursor:pointer;}#mermaid-svg-Oa1yRh73uc2RUc3u .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Oa1yRh73uc2RUc3u .arrowheadPath{fill:#333333;}#mermaid-svg-Oa1yRh73uc2RUc3u .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Oa1yRh73uc2RUc3u .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Oa1yRh73uc2RUc3u .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oa1yRh73uc2RUc3u .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Oa1yRh73uc2RUc3u .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oa1yRh73uc2RUc3u .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster text{fill:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u .cluster span{color:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Oa1yRh73uc2RUc3u .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Oa1yRh73uc2RUc3u rect.text{fill:none;stroke-width:0;}#mermaid-svg-Oa1yRh73uc2RUc3u .icon-shape,#mermaid-svg-Oa1yRh73uc2RUc3u .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oa1yRh73uc2RUc3u .icon-shape p,#mermaid-svg-Oa1yRh73uc2RUc3u .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Oa1yRh73uc2RUc3u .icon-shape .label rect,#mermaid-svg-Oa1yRh73uc2RUc3u .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oa1yRh73uc2RUc3u .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Oa1yRh73uc2RUc3u .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Oa1yRh73uc2RUc3u :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Unicode 三明治

    尽早解码

    尽可能晚编码

    输入字节读文件/接收网络数据

    业务逻辑层纯文本字符串,再不解码编码

    输出字节写文件/发送网络数据

    在 Python 3 中,内置的 open 函数能在读取文件时自动解码,在写入文件时自动编码。

    重要:open 函数应当在调用时显式指定 encoding 参数。依赖系统的默认编码会导致代码在不同环境下行为不一致。

    文本文件处理最佳实践

    # 显式指定编码(推荐)
    with open('example.txt', 'r', encoding='utf-8') as f:
    content = f.read() # 自动解码为 str

    with open('output.txt', 'w', encoding='utf-8') as f:
    f.write(content) # 自动编码为字节

    # 避免这样写(依赖系统默认编码)
    with open('example.txt', 'r') as f: # 可能在不同环境下出错
    content = f.read()

    八、为了正确比较而规范化 Unicode 字符串

    8.1 规范化的重要性

    Unicode 有组合字符(combining characters)——附加到前一个字符上的音调或其他标记,在打印时看似一个字符,但在底层可能是由多个码位组合而成的。下面是一个典型的范例。

    # 同一个字符的不同表示方式
    s1 = '\\xc7' # 预组合字符 U+00C7 'Ç'
    s2 = 'C' + '\\u0327' # 基础字母 C + 组合符 U+0327 下软尾符
    print(s1, s2) # Ç Ç(视觉上看起来一样)
    print(s1 == s2) # False(码位序列不同)

    # 标准化后就可以正确比较
    import unicodedata
    print(unicodedata.normalize('NFC', s1) == unicodedata.normalize('NFC', s2)) # True

    Unicode 规范化规则的主要价值在于:让语义上相同的字符即使码位的表现形式不同,也能够在比较时被视为相等。

    8.2 规范化方式

    unicodedata.normalize 的第一个参数决定标准化方式:

    规范化形式全称说明
    NFC Normalization Form C 将分解的字符组合成最短的等价形式(推荐)
    NFD Normalization Form D 将组合字符分解为基础字符和单独的组合字符
    NFKC Normalization Form KC 在 NFC 的基础上增加兼容分解,如将 fi 分解为 fi
    NFKD Normalization Form KD 在 NFD 的基础上增加兼容分解

    import unicodedata

    # NFC:组合(推荐)
    s = 'é' # 预组合字符
    nfc = unicodedata.normalize('NFC', s)
    print(f"NFC: {nfc}")

    # NFD:分解
    nfd = unicodedata.normalize('NFD', s)
    print(f"NFD: {nfd}")

    # 兼容组合示例
    s2 = 'fi' # 连字(兼容字符)
    print(unicodedata.normalize('NFKC', s2)) # 'fi'(拆分为基础字母)

    移除变音符号的实现方式:使用 NFD 规范化后,只保留 ASCII 部分的字符:

    import unicodedata

    def remove_diacritics(text):
    """移除文本中的变音符号(如 é → e)"""
    nfd = unicodedata.normalize('NFKD', text)
    return ''.join(c for c in nfd if not unicodedata.combining(c))

    print(remove_diacritics('café')) # 'cafe'
    print(remove_diacritics('São Paulo')) # 'Sao Paulo'

    8.3 大小写折叠(case folding)

    str.casefold() 是一个专用于不区分大小写匹配的方法,比 str.lower() 更激进。例如,德语字母 ß 经过 casefold() 后会变成 ss。

    # lower() 与 casefold() 的区别
    german = 'Straße'
    print(german.lower()) # 'straße'
    print(german.casefold()) # 'strasse'(更适合于不区分大小写的匹配)

    # 不区分大小写的比较
    def case_folded_equals(s1, s2):
    return unicodedata.normalize('NFC', s1.casefold()) == unicodedata.normalize('NFC', s2.casefold())

    九、Unicode 文本排序

    Python 中排序任何类型的序列时,通过比较序列中各项的元素(默认基于码位)进行。这种按码位排序的方式对于非 ASCII 文本常常不符合人类预期的字母顺序。

    # 按码位排序(通常不是想要的顺序)
    fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
    print(sorted(fruits)) # ['acerola', 'atemoia', 'açaí', 'cajá', 'caju']

    locale.strxfrm 函数将字符串转换为可用于区域感知比较的形式,可作为 sorted 的 key 函数使用。

    import locale

    # 设置区域(需要系统支持)
    locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
    fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
    sorted_fruits = sorted(fruits, key=locale.strxfrm)
    print(sorted_fruits) # 符合葡萄牙语排序顺序

    # 注意:locale 可能不可用或者在不同环境下实现不一致
    # 替代方案:PyUCA(Unicode Collation Algorithm)库

    对于跨平台一致性要求较高的场景,建议使用第三方库 PyUCA(Unicode Collation Algorithm)进行排序。

    十一、支持字符串和字节序列的双模式 API

    11.1 正则表达式中的双模式

    Python 的 re 模块同时支持字符串模式(str)和字节序列模式(bytes),两者行为略有差异。

    import re

    # 字符串模式(处理 Unicode 字符)
    text = 'café'
    pattern = r'\\w+'
    print(re.findall(pattern, text)) # ['café']

    # 字节序列模式(处理 ASCII 数据范围)
    byte_text = b'caf\\xc3\\xa9'
    try:
    # 字节模式不能直接匹配非 ASCII 字节,上面提到的正则表达式元字符只会匹配 ASCII 数据
    print(re.findall(b'\\w+', byte_text))
    except UnicodeDecodeError as e:
    print(f"字节模式错误: {e}")

    11.2 os 函数中的双模式

    os 模块的一些函数可以接受字符串或字节序列路径,并根据参数类型返回相应类型的返回值。

    import os

    # 字符串模式
    str_path = '.'
    print(os.listdir(str_path)) # 返回字符串列表

    # 字节序列模式
    bytes_path = b'.'
    print(os.listdir(bytes_path)) # 返回字节序列列表

    十二、本章思维导图

    #mermaid-svg-cqN4Sk32ofseCH5C{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cqN4Sk32ofseCH5C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cqN4Sk32ofseCH5C .error-icon{fill:#552222;}#mermaid-svg-cqN4Sk32ofseCH5C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cqN4Sk32ofseCH5C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cqN4Sk32ofseCH5C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cqN4Sk32ofseCH5C .marker.cross{stroke:#333333;}#mermaid-svg-cqN4Sk32ofseCH5C svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cqN4Sk32ofseCH5C p{margin:0;}#mermaid-svg-cqN4Sk32ofseCH5C .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cqN4Sk32ofseCH5C .cluster-label text{fill:#333;}#mermaid-svg-cqN4Sk32ofseCH5C .cluster-label span{color:#333;}#mermaid-svg-cqN4Sk32ofseCH5C .cluster-label span p{background-color:transparent;}#mermaid-svg-cqN4Sk32ofseCH5C .label text,#mermaid-svg-cqN4Sk32ofseCH5C span{fill:#333;color:#333;}#mermaid-svg-cqN4Sk32ofseCH5C .node rect,#mermaid-svg-cqN4Sk32ofseCH5C .node circle,#mermaid-svg-cqN4Sk32ofseCH5C .node ellipse,#mermaid-svg-cqN4Sk32ofseCH5C .node polygon,#mermaid-svg-cqN4Sk32ofseCH5C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cqN4Sk32ofseCH5C .rough-node .label text,#mermaid-svg-cqN4Sk32ofseCH5C .node .label text,#mermaid-svg-cqN4Sk32ofseCH5C .image-shape .label,#mermaid-svg-cqN4Sk32ofseCH5C .icon-shape .label{text-anchor:middle;}#mermaid-svg-cqN4Sk32ofseCH5C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cqN4Sk32ofseCH5C .rough-node .label,#mermaid-svg-cqN4Sk32ofseCH5C .node .label,#mermaid-svg-cqN4Sk32ofseCH5C .image-shape .label,#mermaid-svg-cqN4Sk32ofseCH5C .icon-shape .label{text-align:center;}#mermaid-svg-cqN4Sk32ofseCH5C .node.clickable{cursor:pointer;}#mermaid-svg-cqN4Sk32ofseCH5C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cqN4Sk32ofseCH5C .arrowheadPath{fill:#333333;}#mermaid-svg-cqN4Sk32ofseCH5C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cqN4Sk32ofseCH5C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cqN4Sk32ofseCH5C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cqN4Sk32ofseCH5C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cqN4Sk32ofseCH5C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cqN4Sk32ofseCH5C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cqN4Sk32ofseCH5C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cqN4Sk32ofseCH5C .cluster text{fill:#333;}#mermaid-svg-cqN4Sk32ofseCH5C .cluster span{color:#333;}#mermaid-svg-cqN4Sk32ofseCH5C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cqN4Sk32ofseCH5C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cqN4Sk32ofseCH5C rect.text{fill:none;stroke-width:0;}#mermaid-svg-cqN4Sk32ofseCH5C .icon-shape,#mermaid-svg-cqN4Sk32ofseCH5C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cqN4Sk32ofseCH5C .icon-shape p,#mermaid-svg-cqN4Sk32ofseCH5C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cqN4Sk32ofseCH5C .icon-shape .label rect,#mermaid-svg-cqN4Sk32ofseCH5C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cqN4Sk32ofseCH5C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cqN4Sk32ofseCH5C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cqN4Sk32ofseCH5C :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第4章 文本和字节序列

    Unicode基础

    字符

    码位 U+xxxx

    编码 码位→字节

    解码 字节→码位

    二进制序列

    bytes 不可变

    bytearray 可变

    元素 0-255切片类型不变长度1不退化

    memoryview 零复制内存视图

    struct 打包/解包C结构

    编解码器

    utf-8 最常用

    latin1 / iso8859_1

    gb2312 中文编码

    UTF-16 含BOM

    编解码错误

    UnicodeEncodeErrorstr→bytes

    UnicodeDecodeErrorbytes→str

    chardet 编码检测

    文本文件最佳实践

    Unicode三明治

    尽早解码

    业务只用str

    尽量晚编码

    open显式指定encoding

    文本规范化

    NFD / NFC / NKFC / NFKD

    casefold 大小写折叠

    locale.strxfrm 区域排序

    双模式API

    re正则 str/bytes双模式

    os函数 参数决定返回类型

    十三、常见错误与最佳实践

    常见错误

    错误现象原因解决方案
    UnicodeEncodeError 字符串中包含目标编码不支持的字符 使用 errors 参数;或改用支持该字符的编码(如 UTF-8)
    UnicodeDecodeError 解码时使用了错误的编码 确定正确的编码再解码;使用 errors='replace' 显示占位符
    SyntaxError 源码中出现非 ASCII 字符 Python 源文件编码与系统默认编码不一致 Python 3 默认 UTF-8,需确保编辑器也以 UTF-8 保存
    在 Python 2 代码中混用 str 与 bytes Python 2 中 str 本质是 bytes,unicode 才是文本 升级到 Python 3,或在 Python 2 中使用 from __future__ import unicode_literals
    网络传输时对 bytes 再次编码 传输的数据已经是 bytes,不能再调用 encode() 检查数据类型,bytes 直接发送,str 先编码

    最佳实践总结

  • 坚持使用 Unicode 三明治:输入时尽快解码为 str,业务逻辑只使用 str,输出时尽量晚编码
  • 显式指定编码:所有涉及文件读写的地方显式传递 encoding 参数
  • 统一使用 UTF-8:没有特殊约束的情况下优先使用 UTF-8,保证最大兼容性
  • 规范化后比较字符串:比较之前用 unicodedata.normalize('NFC', …) 标准化,避免因组合字符导致的惊愕
  • 使用 casefold() 进行不区分大小写比较:而不是简单地调用 lower() 或 upper()
  • 通过 locale.strxfrm 对非 ASCII 文本排序:获得符合地区惯例的结果
  • 不可断然认为可以凭空猜出一个字节序列的原始编码:必须由外界提供或通过 chardet 辅助推断
  • 十四、小结

    第 4 章“文本和字节序列”细致地处理了 Unicode 与二进制字符集的重要话题,建立起了从文本到字节的正确映射关系:

    • str 和 bytes 之间的界限必须区分:str 是文本,bytes 是二进制数据。它们之间的转换必须显式完成
    • 编码是把“人类使用的文本”变成“计算机处理的字节”的过程;解码是字节还原为文本的过程。
    • Python 的 bytes 和 bytearray 支持对二进制数据的灵活处理,切片会保留原始类型
    • memoryview 提供零复制共享内存访问能力,适合大批量二进制数据的高效操作
    • struct 模块在二进制数据与 C 结构体之间解包/打包,处理底层字节流
    • 处理文本的最佳实践是 Unicode 三明治——尽早解码,中间层只处理 str,尽可能晚编码
    • Unicode 规范化(unicodedata.normalize)解决语义相同但码位不同造成的比较问题
    • 排序非 ASCII 文本应考虑使用 locale.strxfrm 或 PyUCA 进行区域感知排序

    Python 3 完全区分了人类可读的文本和底层的字节流,这是解决各种编码问题的根基。经过本章的学习,你应该能够轻松应对与字符和编码相关的错误,写出在各种平台上稳定运行的、不会随处乱码的程序。

    十五、下一章预告

    第 5 章《数据类构建器》(Data Class Builders)

    在第 3 章熟悉了字典的强大扩展能力,在第 4 章掌握了文本和字节的区别之后,接下来我们将面临一个常见的问题:如何在 Python 中更方便地创建表示数据的简单类?

    下一章将从以下角度展开:

    • collections.namedtuple:一个轻量级的不可变类工厂,从第 2 章就见过,这里会深度挖掘它的局限和最佳使用场景
    • typing.NamedTuple:带上类型注解的 namedtuple 变种,是静态分析和 IDE 辅助的好帮手
    • @dataclass 装饰器(Python 3.7+):用更少的代码实现和普通类一样灵活的——同时支持可变属性、默认工厂、__post_init__ 钩子等方法
    • 三种数据类构建器的对比:namedtuple vs NamedTuple vs dataclass
    • 何时选用哪个构建器;如何为数据建模简化代码、提高可维护性

    第 5 章将是编写 Pythonic 数据类的全方位指南,帮助你在日常的程序设计中明智地选择最合适的工具。


    本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
    👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

    赞(0)
    未经允许不得转载:171主机测评 » 《流畅的Python》读书笔记05: 第一部分 数据结构 - 文本和字节序列
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址