您当前位置:首页 深入 Python 3

难度等级:♦♦♦♢♢

字符串

我告诉你这件事是因为你是我的朋友之一。
我的字母表从你的字母表结束的地方开始!
— 苏斯博士,《斑马之后》

 

在您深入研究之前,您需要了解一些枯燥的事情

很少有人会想到这一点,但文本非常复杂。从字母表开始。布干维尔岛的居民拥有世界上最小的字母表;他们的罗托卡斯字母表只包含 12 个字母:A、E、G、I、K、O、P、R、S、T、U 和 V。在另一个极端,汉语、日语和韩语等语言有数千个字符。当然,英语有 26 个字母 — 如果算上大写和小写,则有 52 个 — 再加上一些 !@#$%& 标点符号。

当您谈论“文本”时,您可能在想“计算机屏幕上的字符和符号”。但计算机并不处理字符和符号;它们处理的是位和字节。您在计算机屏幕上看到的每一段文本实际上都存储在特定的 *字符编码* 中。粗略地说,字符编码提供了一种映射关系,将您在屏幕上看到的东西映射到您的计算机实际存储在内存和磁盘中的东西。存在许多不同的字符编码,其中一些针对俄语、汉语或英语等特定语言进行了优化,而另一些则可以用于多种语言。

实际上,情况比这更复杂。许多字符在多个编码中是通用的,但每个编码可能会使用不同的字节序列来实际存储内存或磁盘中的这些字符。因此,您可以将字符编码视为一种解密密钥。每当有人向您提供一个字节序列 — 一个文件、一个网页,任何东西 — 并声称它是“文本”时,您都需要知道他们使用了什么字符编码,以便您可以将字节解码为字符。如果他们给您错误的密钥或根本没有密钥,那么您将不得不承担自行破解代码的艰巨任务。您很有可能会出错,结果将是乱码。

您肯定见过这样的网页,其中一些奇怪的类似问号的字符出现在应该出现撇号的地方。这通常意味着页面作者没有正确声明他们的字符编码,您的浏览器只能猜测,结果是混合了预期和意外的字符。在英语中,这仅仅是令人讨厌;在其他语言中,结果可能是完全不可读的。

世界上每种主要语言都有字符编码。由于每种语言都不同,并且内存和磁盘空间在历史上一直很昂贵,因此每种字符编码都针对特定语言进行了优化。我的意思是,每个编码使用相同的数字(0–255)来表示该语言的字符。例如,您可能熟悉 ASCII 编码,它将英语字符存储为介于 0 到 127 之间的数字。(65 是大写“A”,97 是小写“a”,&c。)英语的字母表非常简单,因此可以在不到 128 个数字中完全表达出来。对于那些能用二进制计数的人来说,这占一个字节的 8 位中的 7 位。

法语、西班牙语和德语等西欧语言比英语有更多字母。或者,更准确地说,它们有带各种变音符号的字母,例如西班牙语中的 ñ 字符。这些语言最常用的编码是 CP-1252,也称为“windows-1252”,因为它在 Microsoft Windows 上得到广泛使用。CP-1252 编码在 0–127 范围内与 ASCII 共享字符,但随后扩展到 128–255 范围,用于 n-with-a-tilde-over-it(241)、u-with-two-dots-over-it(252)等字符。它仍然是单字节编码,尽管如此;最高可能数 255 仍然适合一个字节。

然后是汉语、日语和韩语等语言,它们拥有如此多的字符,以至于需要多字节字符集。也就是说,每个“字符”都由一个介于 0–65535 之间的两字节数字表示。但是,不同的多字节编码仍然与不同的单字节编码存在相同的问题,即它们都使用相同的数字来表示不同的含义。只是数字范围更广,因为需要表示的字符更多。

在非联网的世界中,这大体上是可以的,“文本”是您自己键入并偶尔打印的东西。没有太多“纯文本”。源代码是 ASCII,而其他人则使用文字处理器,这些文字处理器定义了自己的(非文本)格式,这些格式跟踪字符编码信息以及丰富的样式,&c。人们使用与原始作者相同的文字处理程序来阅读这些文档,因此一切都或多或少地正常工作。

现在想想电子邮件和网络等全球网络的兴起。大量的“纯文本”在全球各地飞来飞去,在一部计算机上创作,通过第二部计算机传输,并由第三部计算机接收和显示。计算机只能看到数字,但这些数字可能意味着不同的东西。哦,不!该怎么办?好吧,系统必须被设计为将编码信息与每一段“纯文本”一起携带。请记住,它是将计算机可读数字映射到人类可读字符的解密密钥。缺少解密密钥意味着乱码、乱码或更糟糕的结果。

现在想想尝试将多段文本存储在同一个地方,例如在同一个数据库表中,该表包含您收到的所有电子邮件。您仍然需要将字符编码与每一段文本一起存储,以便您可以正确地显示它。觉得很难吗?尝试搜索您的电子邮件数据库,这意味着需要动态地进行多种编码之间的转换。这听起来不令人愉快吗?

现在想想多语言文档的可能性,其中来自多种语言的字符在同一文档中并排出现。(提示:尝试执行此操作的程序通常使用转义码来切换“模式”。砰,您处于俄语 koi8-r 模式,因此 241 表示 Я;砰,现在您处于 Mac 希腊语模式,因此 241 表示 ώ。)当然,您还想搜索*这些*文档。

现在哭吧,因为您对字符串的所有认知都是错误的,而且根本不存在“纯文本”。

Unicode

输入 Unicode

Unicode 是一个旨在表示来自*每种*语言的*每个*字符的系统。Unicode 将每个字母、字符或表意符号表示为一个 4 字节数字。每个数字都表示在世界上至少一种语言中使用的唯一字符。(并非所有数字都被使用,但超过 65535 个数字被使用,因此 2 个字节将不足够。)在多种语言中使用的字符通常具有相同的数字,除非存在合理的词源理由不这样做。无论如何,每个字符只有一个数字,每个数字只有一个字符。每个数字总是只表示一个含义;没有需要跟踪的“模式”。U+0041 始终为 'A',即使您的语言中没有 'A'

从表面上看,这似乎是一个好主意。一个编码统治所有。每个文档中包含多种语言。不再需要“模式切换”来在流中切换编码。但马上,一个显而易见的问题应该跃入您的脑海。四个字节?对于每个字符 这似乎非常浪费,尤其是对于英语和西班牙语等语言,它们只需要不到一个字节(256 个数字)来表达所有可能的字符。实际上,即使对于表意文字语言(如汉语)来说,这也是一种浪费,它们永远不需要超过每个字符两个字节。

确实有一种 Unicode 编码,它使用每个字符四个字节。它被称为 UTF-32,因为 32 位 = 4 个字节。UTF-32 是一种直接编码;它获取每个 Unicode 字符(一个 4 字节数字),并使用该数字来表示该字符。这有一些优点,其中最重要的是您可以以恒定时间找到字符串的第 *N* 个字符,因为第 *N* 个字符从第 *4×N* 个字节开始。它还有一些缺点,最明显的是它需要四个字节来存储每个字符。

尽管存在许多 Unicode 字符,但事实证明,大多数人永远不会使用超过前 65535 个字符。因此,还有另一种 Unicode 编码,称为 UTF-16(因为 16 位 = 2 个字节)。UTF-16 将 0–65535 中的每个字符编码为两个字节,然后使用一些肮脏的技巧,如果您实际上需要表示超过 65535 的很少使用的“星际平面”Unicode 字符。最明显的优点:UTF-16 的空间效率是 UTF-32 的两倍,因为每个字符只需要存储两个字节而不是四个字节(除了那些没有存储的字节)。并且您仍然可以轻松地在恒定时间内找到字符串的第 *N* 个字符,前提是您假设字符串不包含任何星际平面字符,这是一个很好的假设,直到它不再是假设的那一刻。

但 UTF-32 和 UTF-16 也存在一些不明显的缺点。不同的计算机系统以不同的方式存储单个字节。这意味着字符 U+4E2D 可以以 UTF-16 形式存储为 4E 2D2D 4E,具体取决于系统是大端还是小端。(对于 UTF-32,还有更多可能的字节顺序。)只要您的文档从未离开您的计算机,您就安全了 — 同一台计算机上的不同应用程序都将使用相同的字节顺序。但一旦您想要在系统之间传输文档,可能是在某种全球网络上,您将需要一种方法来指示您的字节以何种顺序存储。否则,接收系统将无法知道两个字节序列 4E 2D 是指 U+4E2D 还是 U+2D4E

为了解决*这个问题*,多字节 Unicode 编码定义了“字节顺序标记”,这是一个特殊的不可打印字符,您可以将其包含在文档的开头,以指示您的字节的顺序。对于 UTF-16,字节顺序标记为 U+FEFF。如果您收到以字节 FF FE 开头的 UTF-16 文档,您就知道字节顺序是一种方式;如果它以 FE FF 开头,您就知道字节顺序已反转。

尽管如此,UTF-16 并不完全理想,尤其是当您处理大量 ASCII 字符时。如果您仔细想想,即使是中文网页也会包含大量 ASCII 字符 — 所有围绕可打印中文字符的元素和属性。能够以恒定时间找到第 *N* 个字符很好,但仍然存在那些星际平面字符的烦人问题,这意味着您无法*保证*每个字符正好是两个字节,因此您无法*真正*在恒定时间内找到第 *N* 个字符,除非您维护一个单独的索引。而且,世界上确实存在大量 ASCII 文本……

其他人思考了这些问题,他们找到了解决方案

UTF-8

UTF-8 是一种用于 Unicode 的 *可变长度* 编码系统。也就是说,不同的字符占用不同的字节数。对于 ASCII 字符(A-Z,&c.),UTF-8 每个字符只使用一个字节。事实上,它使用了完全相同的字节;UTF-8 中的前 128 个字符(0-127)与 ASCII 不可区分。“扩展拉丁语”字符(如 ñ 和 ö)最终会占用两个字节。(这些字节并不像 UTF-16 中那样仅仅是 Unicode 代码点;它们涉及到一些复杂的位操作。)汉字,如 中,最终会占用三个字节。很少使用的“天文平面”字符会占用四个字节。

缺点:由于每个字符可能占用不同的字节数,因此查找第 N 个字符是一个 O(N) 操作 - 也就是说,字符串越长,查找特定字符所需的时间就越长。此外,还需要进行位操作才能将字符编码为字节,并将字节解码为字符。

优点:对常见的 ASCII 字符进行超级高效的编码。对于扩展拉丁语字符,不比 UTF-16 差。对于汉字来说,比 UTF-32 更好。此外(你必须相信我,因为我不会向你展示数学),由于位操作的精确性,不存在字节顺序问题。在任何计算机上,使用 UTF-8 编码的文档都会使用完全相同的字节流。

深入了解

在 Python 3 中,所有字符串都是 Unicode 字符的序列。不存在用 UTF-8 编码的 Python 字符串,或者用 CP-1252 编码的 Python 字符串。“这个字符串是 UTF-8 吗?”是一个无效的问题。UTF-8 是一种将字符编码为字节序列的方法。如果你想将一个字符串转换为特定字符编码的字节序列,Python 3 可以帮你做到。如果你想将一个字节序列转换为字符串,Python 3 也能帮你做到。字节不是字符;字节就是字节。字符是一种抽象概念。字符串是这些抽象概念的序列。

>>> s = '深入 Python' 
>>> len(s) 
9
>>> s[0] 
'深'
>>> s + ' 3' 
'深入 Python 3'
  1. 要创建字符串,请用引号将其括起来。Python 字符串可以用单引号 (') 或双引号 (") 定义。
  2. 内置的 len() 函数返回字符串的长度,即字符数量。这与用于 查找列表、元组、集合或字典长度 的函数相同。字符串就像一个字符元组。
  3. 就像从列表中获取单个项目一样,你可以使用索引表示法从字符串中获取单个字符。
  4. 与列表一样,你可以使用 + 运算符将字符串 *连接* 起来。

格式化字符串

让我们再看看 humansize.py

[下载 humansize.py]

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],         
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.                          

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''                                                                     
    if size < 0:
        raise ValueError('number must be non-negative')                     

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)                       

    raise ValueError('number too large')
  1. 'KB''MB''GB'……这些都是字符串。
  2. 函数文档字符串是字符串。此文档字符串跨越多行,因此它使用三个引号来开始和结束字符串。
  3. 这三个引号结束了文档字符串。
  4. 还有一个字符串,作为人类可读的错误消息传递给异常。
  5. 有一个……哇,那是什么鬼东西?

Python 3 支持将值 *格式化* 为字符串。虽然这可能包括非常复杂的表达式,但最基本的用法是用一个占位符将一个值插入到一个字符串中。

>>> username = 'mark'
>>> password = 'PapayaWhip' 
>>> "{0}'s password is {1}".format(username, password) 
"mark's password is PapayaWhip"
  1. 不,我的密码实际上不是 PapayaWhip
  2. 这里有很多事情要发生。首先,这是一个对字符串文字的调用方法。*字符串是对象*,对象有方法。其次,整个表达式计算为一个字符串。第三,{0}{1} 是 *替换字段*,它们被传递给 format() 方法的参数替换。

复合字段名

前面的示例展示了最简单的情况,其中替换字段只是整数。整数替换字段被视为 format() 方法参数列表中的位置索引。这意味着 {0} 被第一个参数(在本例中为 username)替换,{1} 被第二个参数(password)替换,等等。你可以拥有与参数数量一样多的位置索引,并且可以拥有任意数量的参数。但替换字段比这要强大得多。

>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000] 
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes) 
'1000KB = 1MB'
  1. 你没有调用 humansize 模块中的任何函数,而是只是获取了它定义的数据结构之一:“SI”(1000 的幂)后缀列表。
  2. 这看起来很复杂,但实际上并不复杂。{0} 将引用传递给 format() 方法的第一个参数 si_suffixes。但 si_suffixes 是一个列表。所以 {0[0]} 指的是列表的第一个元素,它是传递给 format() 方法的第一个参数:'KB'。同时,{0[1]} 指的是同一个列表的第二个元素:'MB'。花括号之外的所有内容 - 包括 1000、等号和空格 - 都保持不变。最终结果是字符串 '1000KB = 1MB'

此示例展示了 *格式说明符可以使用(几乎)Python 语法访问数据结构的项目和属性*。这被称为 *复合字段名*。以下复合字段名“直接起作用”

为了让你大开眼界,这里有一个示例,它将所有这些都结合起来

>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'

以下是它的工作原理

格式说明符

等等!还有更多!让我们再看看 humansize.py 中的那行奇怪的代码

if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1} 被传递给 format() 方法的第二个参数替换,该参数是 suffix。但 {0:.1f} 是什么呢?它是两件事:{0},你认识,和 :.1f,你不认识。后半部分(包括冒号及以后的部分)定义了 *格式说明符*,它进一步细化了替换变量的格式化方式。

格式说明符允许你以各种有用的方式修改替换文本,例如 C 语言中的 printf() 函数。你可以添加零填充或空格填充、对齐字符串、控制小数精度,甚至将数字转换为十六进制。

在替换字段中,冒号 (:) 表示格式说明符的开始。格式说明符 “.1” 表示“四舍五入到最接近的十分之一”(即仅显示小数点后一位)。格式说明符 “f” 表示“定点数字”(与指数表示法或其他十进制表示法相反)。因此,给定 size698.24suffix'GB',格式化的字符串将为 '698.2 GB',因为 698.24 被四舍五入到小数点后一位,然后后缀被附加到数字之后。

>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

有关格式说明符的所有详细信息,请参阅官方 Python 文档中的格式说明符迷你语言。

其他常见字符串方法

除了格式化之外,字符串还可以执行许多其他有用的技巧。

>>> s = '''Finished files are the re- 
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines() 
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower()) 
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f') 
6
  1. 你可以在 Python 交互式 shell 中输入 *多行* 字符串。一旦你用三个引号开始一个多行字符串,只需按下 ENTER,交互式 shell 就会提示你继续输入字符串。输入结束的三个引号结束字符串,下一个 ENTER 将执行命令(在本例中,将字符串分配给 s)。
  2. splitlines() 方法接受一个多行字符串,并返回一个字符串列表,每个字符串对应原始字符串的一行。请注意,每行末尾的回车符不包括在内。
  3. lower() 方法将整个字符串转换为小写。(类似地,upper() 方法将字符串转换为大写。)
  4. count() 方法计算子字符串出现的次数。是的,那句话中确实有六个 “f”!

这里还有一个常见的情况。假设你有一个以 key1=value1&key2=value2 形式表示的键值对列表,你想将它们拆分并创建一个以 {key1: value1, key2: value2} 形式表示的字典。

>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&') 
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v] 
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists) 
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. split() 字符串方法有一个必需的参数,即分隔符。该方法根据分隔符将字符串拆分为字符串列表。这里,分隔符是 ampersand 字符,但它可以是任何东西。
  2. 现在我们有一个字符串列表,每个字符串都有一个键,后面跟着一个等号,然后是一个值。我们可以使用 列表推导 来遍历整个列表,并根据第一个等号将每个字符串拆分为两个字符串。split() 方法的可选第二个参数是要拆分的次数。1 表示“只拆分一次”,因此 split() 方法将返回一个包含两个元素的列表。(理论上,一个值也可以包含等号。如果你只使用 'key=value=foo'.split('='),你最终会得到一个包含三个元素的列表 ['key', 'value', 'foo']。)
  3. 最后,Python 可以通过将列表列表传递给dict() 函数,轻松地将列表列表转换为字典。

上面的示例看起来很像解析 URL 中的查询参数,但实际生活中的 URL 解析比这复杂得多。如果你正在处理 URL 查询参数,你最好使用urllib.parse.parse_qs() 函数,它可以处理一些不明显的边缘情况。

字符串切片

定义字符串后,你可以获取字符串的任何部分作为新的字符串。这称为对字符串进行切片。字符串切片的工作方式与列表切片完全相同,这是有道理的,因为字符串只是字符序列。

>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11] 
'alphabet'
>>> a_string[3:-3] 
'alphabet starts where your alphabet en'
>>> a_string[0:2] 
'My'
>>> a_string[:18] 
'My alphabet starts'
>>> a_string[18:] 
' where your alphabet ends.'
  1. 你可以通过指定两个索引来获取字符串的一部分,称为“切片”。返回值是一个新的字符串,包含字符串中的所有字符,按顺序排列,从第一个切片索引开始。
  2. 与列表切片一样,你可以使用负索引对字符串进行切片。
  3. 字符串是从零开始索引的,因此a_string[0:2] 返回字符串的前两个项,从a_string[0] 开始,到a_string[2] 之前的项。
  4. 如果左侧切片索引为 0,则可以省略它,0 为隐式。因此a_string[:18]a_string[0:18] 相同,因为起始 0 为隐式。
  5. 类似地,如果右侧切片索引为字符串的长度,则可以省略它。因此a_string[18:]a_string[18:44] 相同,因为此字符串有 44 个字符。这里有一个令人愉快的对称性。在这个 44 个字符的字符串中,a_string[:18] 返回前 18 个字符,而a_string[18:] 返回除前 18 个字符之外的所有字符。事实上,a_string[:n] 将始终返回前 n 个字符,而a_string[n:] 将返回其余字符,无论字符串的长度如何。

字符串与字节

字节 是字节;字符是一种抽象。不可变的 Unicode 字符序列称为字符串。不可变的 0 到 255 之间的数字序列称为字节 对象。

>>> by = b'abcd\x65' 
>>> by
b'abcde'
>>> type(by) 
<class 'bytes'>
>>> len(by) 
5
>>> by += b'\xff' 
>>> by
b'abcde\xff'
>>> len(by) 
6
>>> by[0] 
97
>>> by[0] = 102 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. 要定义bytes 对象,请使用b''字节 字面量”语法。字节字面量中的每个字节可以是 ASCII 字符或从\x00\xff(0-255)的编码十六进制数。
  2. bytes 对象的类型是bytes
  3. 与列表和字符串一样,你可以使用内置的len() 函数获取bytes 对象的长度。
  4. 与列表和字符串一样,你可以使用+ 运算符连接bytes 对象。结果是一个新的bytes 对象。
  5. 连接 5 字节bytes 对象和 1 字节bytes 对象将得到一个 6 字节bytes 对象。
  6. 与列表和字符串一样,你可以使用索引表示法获取bytes 对象中的单个字节。字符串的项是字符串;bytes 对象的项是整数。具体来说,是 0 到 255 之间的整数。
  7. bytes 对象是不可变的;你不能分配单个字节。如果你需要更改单个字节,你可以使用字符串切片 和连接运算符(它们的工作方式与字符串相同),或者可以将bytes 对象转换为bytearray 对象。
>>> by = b'abcd\x65'
>>> barr = bytearray(by) 
>>> barr
bytearray(b'abcde')
>>> len(barr) 
5
>>> barr[0] = 102 
>>> barr
bytearray(b'fbcde')
  1. 要将bytes 对象转换为可变的bytearray 对象,请使用内置的bytearray() 函数。
  2. 你可以在bytes 对象上执行的所有方法和操作,也可以在bytearray 对象上执行。
  3. 唯一不同的是,对于bytearray 对象,你可以使用索引表示法分配单个字节。分配的值必须是 0 到 255 之间的整数。

永远不能做的一件事就是混合字节和字符串。

>>> by = b'd'
>>> s = 'abcde'
>>> by + s 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii')) 
1
  1. 你不能连接字节和字符串。它们是两种不同的数据类型。
  2. 你不能计算字符串中字节的出现次数,因为字符串中没有字节。字符串是字符序列。也许你指的是“计算你使用特定字符编码对这些字节序列进行解码后得到的字符串的出现次数”?好吧,你需要明确说明。Python 3 不会隐式将字节转换为字符串或将字符串转换为字节。
  3. 巧合的是,这行代码表示“计算你使用特定字符编码对这些字节序列进行解码后得到的字符串的出现次数”。

字符串和字节之间的联系:bytes 对象有一个decode() 方法,它接受一个字符编码并返回一个字符串,而字符串有一个encode() 方法,它接受一个字符编码并返回一个bytes 对象。在前面的示例中,解码相对简单——将 ASCII 编码中的字节序列转换为字符字符串。但相同的过程适用于任何支持字符串字符的编码,即使是传统(非 Unicode)编码。

>>> a_string = '深入 Python' 
>>> len(a_string)
9
>>> by = a_string.encode('utf-8') 
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030') 
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5') 
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5') 
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. 这是一个字符串。它有九个字符。
  2. 这是一个bytes 对象。它有 13 个字节。这是你将a_stringUTF-8 编码后得到的字节序列。
  3. 这是一个bytes 对象。它有 11 个字节。这是你将a_string 以 GB18030 编码后得到的字节序列。
  4. 这是一个bytes 对象。它有 11 个字节。这是你将a_string 以 Big5 编码后得到的完全不同的字节序列
  5. 这是一个字符串。它有九个字符。这是你使用 Big5 编码算法对by 进行解码后得到的字符序列。它与原始字符串相同。

附录:Python 源代码的字符编码

Python 3 假设你的源代码(即每个.py 文件)以 UTF-8 编码。

在 Python 2 中,.py 文件的默认编码为 ASCII。在 Python 3 中,默认编码为 UTF-8.

如果你想在 Python 代码中使用其他编码,可以在每个文件的首行放置一个编码声明。此声明将定义.py 文件为 windows-1252

# -*- coding: windows-1252 -*-

从技术上讲,如果第一行是类似 UNIX 的哈希邦命令,则字符编码覆盖也可以放在第二行。

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

有关更多信息,请参阅 PEP 263:定义 Python 源代码编码。

进一步阅读

关于 Python 中的 Unicode

关于一般意义上的 Unicode

关于其他格式中的字符编码

关于字符串和字符串格式

© 2001–11 Mark Pilgrim