关于Python编码这一篇文章就够了

5次阅读

共计 6815 个字符,预计需要花费 18 分钟才能阅读完成。

概述

在使用 Python 或者其他的编程语言,都会多多少少遇到编码错误,处理起来非常痛苦。在 Stack Overflow 和其他的编程问答网站上,UnicodeDecodeError 和 UnicodeEncodeError 也经常被提及。本篇教程希望能帮你认识 Python 编码,并能够从容的处理编码问题。

本教程提到的编码知识并不限定在 Python,其他语言也大同小异,但我们依然会以 Python 为主,来演示和讲解编码知识。

通过该教程,你将学习到如下的知识:

  • 获取有关字符编码和数字系统的概念
  • 理解编码如何使用 Python 的 str 和 bytes
  • 通过 int 函数了解 Python 对数字系统的支持
  • 熟悉 Python 字符编码和数字系统相关的内置函数

什么是字符编码

现在的编码规则已经有好多了,最简单、最基本是的 ASCII 编码,只要是你学过计算机相关的课程,你就应该多少了解一点 ASCII 编码,他是最小也是最适合了解字符编码原理的编码规则。具体如下:

  • 小写英文字符:a-z
  • 大写英文字符:A-Z
  • 符号: 比如 $ 和!
  • 空白符:回车、换行、空格等
  • 一些不可打印的字符: 比如 b 等

那么,字符编码的定义到底是什么了?它是一种将字符(如字母,标点符号,符号,空格和控制字符)转换为整数并最终转换为 bit 进行存储的方法。每个字符都可以编码为唯一的 bit 序列。如果你对 bit 的概念不了解,请不要担心,我们后面会介绍。

ASCII 码的字符被分为如下几组:

ASCII 表一共包括 128 个字符,如果你想了解整个 ASCII 表,这里有

大家在学 python 的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的 Python 学习扣 qun:784,758,214,这里是 python 学习者聚集地

Python string 模块

string 模块是 python 里处理字符串很方便的模块,它包括了整个 ASCII 字符,让我们来看看部分 string 模块源码:

# From lib/python3.7/string.py

whitespace = '\t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

你可以在 Python 中这样使用 string 模块:

>>> import string

>>> s = "What's wrong with ASCII?!?!?">>> s.rstrip(string.punctuation)'What's wrong with ASCII'

什么是 bit

学过计算机相关课程的同学,应该都知道,bit 是计算机内部存储单位,只有 0 和 1 两个状态 (二进制),我们上面所说的 ASCII 表,都是一个 10 进制的数字表示一个字符,而这个 10 进制数字,最终会转换成 0 和 1,存储在计算机内部。例如 (第一列是 10 进制数字,第二列是二进制,第三列是计算机内部存储结果):

这是一种在 Python 中将 ASCII 字符串表示为位序列的方便方法。ASCII 字符串中的每个字符都被伪编码为 8 位,8 位序列之间有空格,每个字符代表一个字符:

>>> def make_bitseq(s: str) -> str:
...     if not s.isascii():
...         raise ValueError("ASCII only allowed")
...     return "".join(f"{ord(i):08b}" for i in s)

>>> make_bitseq("bits")
'01100010 01101001 01110100 01110011'

>>> make_bitseq("CAPS")
'01000011 01000001 01010000 01010011'

>>> make_bitseq("$25.43")
'00100100 00110010 00110101 00101110 00110100 00110011'

>>> make_bitseq("~5")
'01111110 00110101'

我们也可以是用 python 的 f -string 来格式化,比如 f ”{ord(i):08b}”:

  • 冒号的左侧是 ord(i),它是实际的对象,其值将被格式化并插入到输出中。使用 ord()为单个 str 字符提供了 base-10 代码点。
  • 冒号的右侧是格式说明符。08 表示宽度为 8,0 填充,b 用作在基数 2(二进制)中输出结果数的符号。

ASCII 编码不够用了

ASCII 采用的是 8bit 来存储字符 (只使用 7 位,剩下的 1 位二进制为 0),所以,ASCII 最多存储 128 个字符,这有个简单的公式,计算存储字符的 bit 数量与存储字符总数的关系:2 的 n 次方,n 表示 bit 数量。例如:

  • 1bit 存储 2 个字符
  • 8bit 存储 256 个字符
  • 64bit 存储 2 的 64 次方 == 18,446,744,073,709,551,616

我们可以写个简单的代码,来计算一下,指定字符数量,至少需要多少 bit 来存储:

>>> from math import ceil, log

>>> def n_bits_required(nvalues: int) -> int:
...     return ceil(log(nvalues) / log(2))

>>> n_bits_required(256)
8

数字系统

在上面的 ASCII 讨论中,您看到每个字符映射到 0 到 127 范围内的整数。但在 CPython 中还有其他的数字系统,通过其他方式是表示数字。除了十进制外,python 还支持以下几个方式:

  • Binary: 2 进制
  • Octal: 8 进制
  • Hexadecimal (hex): 16 进制

你可能要问,为什么有了十进制,还要支持这么多其他进制的数字了?这个取决你的业务场景和操作系统,在 Python 里,把 str 转换成 int,默认是 10 进制的。

>>> int('11')
11
>>> int('11', base=10)  # 10 is already default
11
>>> int('11', base=2)  # Binary
3
>>> int('11', base=8)  # Octal
9
>>> int('11', base=16)  # Hex
17

你可以在赋值时,直接告诉解释器数字的类型,不同进制标表示方法如下:

类型 前缀 示例
n/a n/a 11
二进制 0b 或者 0B 0b11
八进制 0o 或者 0O 0o11
十六进制 0x 或者 0X 0x11
>>> 11
11
>>> 0b11  # 二进制
3
>>> 0o11  # 八进制
9
>>> 0x11  # 16 进制
17

深入 Unicode

正如您所看到的,ASCII 的问题在于它不是一个足够大的字符集来容纳世界上的语言,方言,符号和字形。(这对于英语来说甚至都不够大。)Unicode 从根本上起到与 ASCII 相同的作用,但是 Unicode 拥有更大的存储空间,具有 1,114,112 个可能的字符,能够完全包含世界上所有的语言。事实上,ASCII 是 Unicode 的完美子集。Unicode 表中的前 128 个字符与您合理期望的 ASCII 字符完全对应。

Unicode 本身不是编码,但是有很多遵循 Unicode 编码规范编码,后面讲到的 UTF- 8 就是其中一个。

Unicode vs UTF-8

Unicode 是一种抽象编码标准,而不是编码。这就是 UTF- 8 和其他编码方案发挥作用的地方。Unicode 标准(字符到代码点的映射)从其单个字符集定义了几种不同的编码。UTF- 8 及其较少使用的表兄弟 UTF-16 和 UTF-32 是用于将 Unicode 字符表示为每个字符一个或多个字节的二进制数据的编码格式。我们稍后将讨论 UTF-16 和 UTF-32,但到目前为止,UTF- 8 占据了最大份额。

Python 3 里的编码与解码

Python 3 的 str 类型用于表示人类可读的文本,可以包含任何 Unicode 字符。

相反,字节类型表示二进制数据或原始字节序列,它们本质上没有附加编码。

编码和解码是从一个到另一个的过程:


decode 和 encode 函数,默认编码是 utf-8:

>>> "résumé".encode("utf-8")
b'r\xc3\xa9sum\xc3\xa9'
>>> "El Niño".encode("utf-8")
b'El Ni\xc3\xb1o'

>>> b"r\xc3\xa9sum\xc3\xa9".decode("utf-8")
'résumé'
>>> b"El Ni\xc3\xb1o".decode("utf-8")
'El Niño'

str.encode()的结果是一个 bytes 对象,bytes 对象只允许 ASCII 字符。这就是为什么在调用“ElNiño”.encode(“utf-8”)时,允许 ASCII 兼容的“El”按原样表示,但带有波浪号的 n 被转义为“xc3 xb1”。这个看起来很乱的序列代表两个字节,十六进制为 0xc3 和 0xb1:

>>> "".join(f"{i:08b}"for i in (0xc3, 0xb1))'11000011 10110001'

Python3 一切字符皆 Unicode

  • 默认情况下,Python 3 源代码假定为 UTF-8。这意味着您不需要# – – 编码:UTF-8 – – 位于 Python 3 中.py 文件的顶部。
  • 默认情况下,所有文本(str)都是 Unicode。编码的 Unicode 文本表示为二进制数据(字节)。str 类型可以包含任何文字 Unicode 字符,例如“Δv/Δt”,所有这些字符都将存储为 Unicode。
  • Unicode 字符集中的任何内容都是标识符中的犹太符号,这意味着 résumé=“〜/ Documents / resume.pdf”是有效的,虽然这看起来很花哨。
  • Python 的 re 模块默认为 re.UNICODE 标志而不是 re.ASCII。这意味着,例如,r“w”匹配 Unicode 字符,而不仅仅是 ASCII 字母。
  • str.encode()和 bytes.decode()中的默认编码是 UTF-8。

还有一个更细微的属性,即内置的 open()的默认编码是依赖于平台的,并且取决于 locale.getpreferredencoding()的值:

>>> # Mac OS X High Sierra
>>> import locale
>>> locale.getpreferredencoding()
'UTF-8'

>>> # Windows Server 2012; other Windows builds may use UTF-16
>>> import locale
>>> locale.getpreferredencoding()
'cp1252'

一个关键特性是 UTF- 8 是一种可变长度编码。回想一下关于 ASCII 的部分。扩展 ASCII-land 中的所有内容最多需要一个字节的空间。您可以使用以下生成器表达式快速证明这一点:

>>> all(len(chr(i).encode("ascii")) == 1 for i in range(128))
True

UTF- 8 完全不同。给定的 Unicode 字符可以占用 1 到 4 个字节。以下是占用四个字节的单个 Unicode 字符的示例:

>>> ibrow = "????"
>>> len(ibrow)
1
>>> ibrow.encode("utf-8")
b'\xf0\x9f\xa4\xa8'
>>> len(ibrow.encode("utf-8"))
4

>>> # Calling list() on a bytes object gives you
>>> # the decimal value for each byte
>>> list(b'\xf0\x9f\xa4\xa8')
[240, 159, 164, 168]

这是 len()的一个微妙但重要的特性:

  • 作为 Python str 的单个 Unicode 字符的长度始终为 1,无论它占用多少字节。
  • 编码为字节的相同字符的长度将介于 1 和 4 之间。

UTF-16 和 UTF-32

我们来聊聊 UTF-16 和 UTF-32,在实际的编程实践中,它们和 UTF- 8 区别还是很重要的,下面的通过实例我们来看看具体区别:

>>> letters = "αβγδ"
>>> rawdata = letters.encode("utf-8")
>>> rawdata.decode("utf-8")
'αβγδ'
>>> rawdata.decode("utf-16")  # 
'뇎닎돎듎'

在这种情况下,使用 UTF- 8 编码四个希腊字母然后解码回 UTF-16 中的文本将产生一个完全不同语言(韩语)的文本 str。
此表汇总了 UTF-8,UTF-16 和 UTF-32 下的字节范围或字节数:

编码 长度 (字节) 是否可变
UTF-8 1~4
UTF-16 2~4
UTF-32 4

UTF 系列编码另外一个需要注意的地方是,UTF- 8 编码占用存储空间不一定比 UTF-16 少,因为他们都不是固定长度的。例如

>>> text = "記者 鄭啟源 羅智堅"
>>> len(text.encode("utf-8"))
26
>>> len(text.encode("utf-16"))
22

原因是 U + 0800 到 U + FFFF(十进制的 2048 到 65535)范围内的代码点占用了 UTF- 8 中的三个字节,而 UTF-16 中仅占用了两个字节。
正常情况下,最好不用使用 UTF-16,除非特殊要求,不然 UTF- 8 更加通用。

Python 内建函数

Python 内置了很多与编码相关的函数:

  • ascii()
  • bin()
  • bytes()
  • chr()
  • hex()
  • int()
  • oct()
  • ord()
  • str()

可以分成以下几组:

  • ascii(),bin(),hex() 和 oct(), 第一个是 ascii(),它生成一个仅对象的 ASCII 表示,其中非 ASCII 字符被转义。其余三个分别给出整数的二进制,十六进制和八进制表示。
  • bytes(),str() 和 int() 是各自类型,bytes,str 和 int 的类构造函数。它们各自提供了将输入强制转换为所需类型的方法。例如,如前所述,虽然 int(11.0) 可能更常见,但您可能也会看到 int(‘11’,base = 16)。
  • ord() 和 chr(),ord() 将 str 字符转换为 10 进制,而 chr() 执行相反的操作。

Python 中的其他编码

目前,我们讲了 4 中编码:

  • ASCII
  • UTF-8
  • UTF-16
  • UTF-32

还有其他很多编码,比如 Latin-1(也称作 ISO-8859-1),这是 HTTP 默认的编码,然而 windows 是 Latin- 1 变体,称作 cp1252。
完整的已接受编码列表隐藏在编解码器模块的文档中,该模块是 Python 标准库的一部分。

还有一个有用的公认编码需要注意,即“unicode-escape”。如果您有一个已解码的 str 并希望快速获得其转义的 Unicode 文字的表示,那么您可以在.encode()中指定此编码:

>>> alef = chr(1575)  # Or "\u0627"
>>> alef_hamza = chr(1571)  # Or "\u0623"
>>> alef, alef_hamza
('ا', 'أ')
>>> alef.encode("unicode-escape")
b'\\u0627'
>>> alef_hamza.encode("unicode-escape")
b'\\u0623'

注意外部数据编码

虽然 Python 代码默认使用了 UTF- 8 作为编码,但并不意味着外部输入的数据也是 UTF- 8 编码的,如果这些外部的数据没有指定编码,那么在处理他们是,你就要格外小心了。比如你调用 API 获取数据,正常是用 UTF- 8 去解码,没有问题,但如果突然 API 给你返回这样的数据:

>>> data = b"\xbc cup of flour"
>>> data.decode("utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbc in position 0: invalid start byte

这地方抛出了 UnicodeDecodeError 错误,仔细检查,发现其实数据的编码是 Latin-1。

>>> data.decode("latin-1")
'¼ cup of flour'

如果你对字符串的编码不确定,可以使用 chardet 库来检查字符串编码。

总结

在本文中你已经了解了编码的详细原理,相信你在以后的编程过程中,再遇到编码错误,相信你能比较从容的解决了。

正文完
 0

关于Python编码这一篇文章就够了

5次阅读

共计 6765 个字符,预计需要花费 17 分钟才能阅读完成。

概述

在使用 Python 或者其他的编程语言,都会多多少少遇到编码错误,处理起来非常痛苦。在 Stack Overflow 和其他的编程问答网站上,UnicodeDecodeError 和 UnicodeEncodeError 也经常被提及。本篇教程希望能帮你认识 Python 编码,并能够从容的处理编码问题。

本教程提到的编码知识并不限定在 Python,其他语言也大同小异,但我们依然会以 Python 为主,来演示和讲解编码知识。

通过该教程,你将学习到如下的知识:

  • 获取有关字符编码和数字系统的概念
  • 理解编码如何使用 Python 的 str 和 bytes
  • 通过 int 函数了解 Python 对数字系统的支持
  • 熟悉 Python 字符编码和数字系统相关的内置函数

什么是字符编码

现在的编码规则已经有好多了,最简单、最基本是的 ASCII 编码,只要是你学过计算机相关的课程,你就应该多少了解一点 ASCII 编码,他是最小也是最适合了解字符编码原理的编码规则。具体如下:

  • 小写英文字符:a-z
  • 大写英文字符:A-Z
  • 符号: 比如 $ 和!
  • 空白符:回车、换行、空格等
  • 一些不可打印的字符: 比如 b 等

那么,字符编码的定义到底是什么了?它是一种将字符(如字母,标点符号,符号,空格和控制字符)转换为整数并最终转换为 bit 进行存储的方法。每个字符都可以编码为唯一的 bit 序列。如果你对 bit 的概念不了解,请不要担心,我们后面会介绍。

ASCII 码的字符被分为如下几组:

ASCII 表一共包括 128 个字符,如果你想了解整个 ASCII 表,这里有

Python string 模块

string 模块是 python 里处理字符串很方便的模块,它包括了整个 ASCII 字符,让我们来看看部分 string 模块源码:

# From lib/python3.7/string.py

whitespace = '\t\n\r\v\f'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

你可以在 Python 中这样使用 string 模块:

>>> import string

>>> s = "What's wrong with ASCII?!?!?">>> s.rstrip(string.punctuation)'What's wrong with ASCII'

什么是 bit

学过计算机相关课程的同学,应该都知道,bit 是计算机内部存储单位,只有 0 和 1 两个状态(二进制),我们上面所说的 ASCII 表,都是一个 10 进制的数字表示一个字符,而这个 10 进制数字,最终会转换成 0 和 1,存储在计算机内部。例如(第一列是 10 进制数字,第二列是二进制,第三列是计算机内部存储结果):

这是一种在 Python 中将 ASCII 字符串表示为位序列的方便方法。ASCII 字符串中的每个字符都被伪编码为 8 位,8 位序列之间有空格,每个字符代表一个字符:

>>> def make_bitseq(s: str) -> str:
...     if not s.isascii():
...         raise ValueError("ASCII only allowed")
...     return "".join(f"{ord(i):08b}" for i in s)

>>> make_bitseq("bits")
'01100010 01101001 01110100 01110011'

>>> make_bitseq("CAPS")
'01000011 01000001 01010000 01010011'

>>> make_bitseq("$25.43")
'00100100 00110010 00110101 00101110 00110100 00110011'

>>> make_bitseq("~5")
'01111110 00110101'

我们也可以是用 python 的 f -string 来格式化,比如 f ”{ord(i):08b}”:

  • 冒号的左侧是 ord(i),它是实际的对象,其值将被格式化并插入到输出中。使用 ord()为单个 str 字符提供了 base-10 代码点。
  • 冒号的右侧是格式说明符。08 表示宽度为 8,0 填充,b 用作在基数 2(二进制)中输出结果数的符号。

ASCII 编码不够用了

ASCII 采用的是 8bit 来存储字符(只使用 7 位,剩下的 1 位二进制为 0),所以,ASCII 最多存储 128 个字符,这有个简单的公式,计算存储字符的 bit 数量与存储字符总数的关系:2 的 n 次方,n 表示 bit 数量。例如:

  • 1bit 存储 2 个字符
  • 8bit 存储 256 个字符
  • 64bit 存储 2 的 64 次方 == 18,446,744,073,709,551,616

我们可以写个简单的代码,来计算一下,指定字符数量,至少需要多少 bit 来存储:

>>> from math import ceil, log

>>> def n_bits_required(nvalues: int) -> int:
...     return ceil(log(nvalues) / log(2))

>>> n_bits_required(256)
8

数字系统

在上面的 ASCII 讨论中,您看到每个字符映射到 0 到 127 范围内的整数。但在 CPython 中还有其他的数字系统,通过其他方式是表示数字。除了十进制外,python 还支持以下几个方式:

  • Binary: 2 进制
  • Octal: 8 进制
  • Hexadecimal (hex): 16 进制

你可能要问,为什么有了十进制,还要支持这么多其他进制的数字了?这个取决你的业务场景和操作系统,在 Python 里,把 str 转换成 int,默认是 10 进制的。

>>> int('11')
11
>>> int('11', base=10)  # 10 is already default
11
>>> int('11', base=2)  # Binary
3
>>> int('11', base=8)  # Octal
9
>>> int('11', base=16)  # Hex
17

你可以在赋值时,直接告诉解释器数字的类型,不同进制标表示方法如下:

类型 前缀 示例
n/a n/a 11
二进制 0b 或者 0B 0b11
八进制 0o 或者 0O 0o11
十六进制 0x 或者 0X 0x11
>>> 11
11
>>> 0b11  # 二进制
3
>>> 0o11  # 八进制
9
>>> 0x11  # 16 进制
17

深入 Unicode

正如您所看到的,ASCII 的问题在于它不是一个足够大的字符集来容纳世界上的语言,方言,符号和字形。(这对于英语来说甚至都不够大。)Unicode 从根本上起到与 ASCII 相同的作用,但是 Unicode 拥有更大的存储空间,具有 1,114,112 个可能的字符,能够完全包含世界上所有的语言。事实上,ASCII 是 Unicode 的完美子集。Unicode 表中的前 128 个字符与您合理期望的 ASCII 字符完全对应。

Unicode 本身不是编码,但是有很多遵循 Unicode 编码规范编码,后面讲到的 UTF- 8 就是其中一个。

Unicode vs UTF-8

Unicode 是一种抽象编码标准,而不是编码。这就是 UTF- 8 和其他编码方案发挥作用的地方。Unicode 标准(字符到代码点的映射)从其单个字符集定义了几种不同的编码。UTF- 8 及其较少使用的表兄弟 UTF-16 和 UTF-32 是用于将 Unicode 字符表示为每个字符一个或多个字节的二进制数据的编码格式。我们稍后将讨论 UTF-16 和 UTF-32,但到目前为止,UTF- 8 占据了最大份额。

Python 3 里的编码与解码

Python 3 的 str 类型用于表示人类可读的文本,可以包含任何 Unicode 字符。

相反,字节类型表示二进制数据或原始字节序列,它们本质上没有附加编码。

编码和解码是从一个到另一个的过程:

decode 和 encode 函数,默认编码是 utf-8:

>>> "résumé".encode("utf-8")
b'r\xc3\xa9sum\xc3\xa9'
>>> "El Niño".encode("utf-8")
b'El Ni\xc3\xb1o'

>>> b"r\xc3\xa9sum\xc3\xa9".decode("utf-8")
'résumé'
>>> b"El Ni\xc3\xb1o".decode("utf-8")
'El Niño'

str.encode()的结果是一个 bytes 对象,bytes 对象只允许 ASCII 字符。这就是为什么在调用“ElNiño”.encode(“utf-8”)时,允许 ASCII 兼容的“El”按原样表示,但带有波浪号的 n 被转义为“xc3 xb1”。这个看起来很乱的序列代表两个字节,十六进制为 0xc3 和 0xb1:

>>> "".join(f"{i:08b}"for i in (0xc3, 0xb1))'11000011 10110001'

Python3 一切字符皆 Unicode

  • 默认情况下,Python 3 源代码假定为 UTF-8。这意味着您不需要# – – 编码:UTF-8 – – 位于 Python 3 中.py 文件的顶部。
  • 默认情况下,所有文本(str)都是 Unicode。编码的 Unicode 文本表示为二进制数据(字节)。str 类型可以包含任何文字 Unicode 字符,例如“Δv/Δt”,所有这些字符都将存储为 Unicode。
  • Unicode 字符集中的任何内容都是标识符中的犹太符号,这意味着 résumé=“〜/ Documents / resume.pdf”是有效的,虽然这看起来很花哨。
  • Python 的 re 模块默认为 re.UNICODE 标志而不是 re.ASCII。这意味着,例如,r“w”匹配 Unicode 字符,而不仅仅是 ASCII 字母。
  • str.encode()和 bytes.decode()中的默认编码是 UTF-8。

还有一个更细微的属性,即内置的 open()的默认编码是依赖于平台的,并且取决于 locale.getpreferredencoding()的值:

>>> # Mac OS X High Sierra
>>> import locale
>>> locale.getpreferredencoding()
'UTF-8'

>>> # Windows Server 2012; other Windows builds may use UTF-16
>>> import locale
>>> locale.getpreferredencoding()
'cp1252'

一个关键特性是 UTF- 8 是一种可变长度编码。回想一下关于 ASCII 的部分。扩展 ASCII-land 中的所有内容最多需要一个字节的空间。您可以使用以下生成器表达式快速证明这一点:

>>> all(len(chr(i).encode("ascii")) == 1 for i in range(128))
True

UTF- 8 完全不同。给定的 Unicode 字符可以占用 1 到 4 个字节。以下是占用四个字节的单个 Unicode 字符的示例:

>>> ibrow = "????"
>>> len(ibrow)
1
>>> ibrow.encode("utf-8")
b'\xf0\x9f\xa4\xa8'
>>> len(ibrow.encode("utf-8"))
4

>>> # Calling list() on a bytes object gives you
>>> # the decimal value for each byte
>>> list(b'\xf0\x9f\xa4\xa8')
[240, 159, 164, 168]

这是 len()的一个微妙但重要的特性:

  • 作为 Python str 的单个 Unicode 字符的长度始终为 1,无论它占用多少字节。
  • 编码为字节的相同字符的长度将介于 1 和 4 之间。

UTF-16 和 UTF-32

我们来聊聊 UTF-16 和 UTF-32,在实际的编程实践中,它们和 UTF- 8 区别还是很重要的,下面的通过实例我们来看看具体区别:

>>> letters = "αβγδ"
>>> rawdata = letters.encode("utf-8")
>>> rawdata.decode("utf-8")
'αβγδ'
>>> rawdata.decode("utf-16")  # ????
'뇎닎돎듎'

在这种情况下,使用 UTF- 8 编码四个希腊字母然后解码回 UTF-16 中的文本将产生一个完全不同语言(韩语)的文本 str。
此表汇总了 UTF-8,UTF-16 和 UTF-32 下的字节范围或字节数:

编码 长度(字节) 是否可变
UTF-8 1~4
UTF-16 2~4
UTF-32 4

UTF 系列编码另外一个需要注意的地方是,UTF- 8 编码占用存储空间不一定比 UTF-16 少,因为他们都不是固定长度的。例如

>>> text = "記者 鄭啟源 羅智堅"
>>> len(text.encode("utf-8"))
26
>>> len(text.encode("utf-16"))
22

原因是 U + 0800 到 U + FFFF(十进制的 2048 到 65535)范围内的代码点占用了 UTF- 8 中的三个字节,而 UTF-16 中仅占用了两个字节。
正常情况下,最好不用使用 UTF-16,除非特殊要求,不然 UTF- 8 更加通用。

Python 内建函数

Python 内置了很多与编码相关的函数:

  • ascii()
  • bin()
  • bytes()
  • chr()
  • hex()
  • int()
  • oct()
  • ord()
  • str()

可以分成以下几组:

  • ascii(),bin(),hex()和 oct(), 第一个是 ascii(),它生成一个仅对象的 ASCII 表示,其中非 ASCII 字符被转义。其余三个分别给出整数的二进制,十六进制和八进制表示。
  • bytes(),str()和 int()是各自类型,bytes,str 和 int 的类构造函数。它们各自提供了将输入强制转换为所需类型的方法。例如,如前所述,虽然 int(11.0)可能更常见,但您可能也会看到 int(’11’,base = 16)。
  • ord()和 chr(),ord()将 str 字符转换为 10 进制,而 chr()执行相反的操作。

Python 中的其他编码

目前,我们讲了 4 中编码:

  • ASCII
  • UTF-8
  • UTF-16
  • UTF-32

还有其他很多编码,比如 Latin-1(也称作 ISO-8859-1),这是 HTTP 默认的编码,然而 windows 是 Latin- 1 变体,称作 cp1252。
完整的已接受编码列表隐藏在编解码器模块的文档中,该模块是 Python 标准库的一部分。

还有一个有用的公认编码需要注意,即“unicode-escape”。如果您有一个已解码的 str 并希望快速获得其转义的 Unicode 文字的表示,那么您可以在.encode()中指定此编码:

>>> alef = chr(1575)  # Or "\u0627"
>>> alef_hamza = chr(1571)  # Or "\u0623"
>>> alef, alef_hamza
('ا', 'أ')
>>> alef.encode("unicode-escape")
b'\\u0627'
>>> alef_hamza.encode("unicode-escape")
b'\\u0623'

注意外部数据编码

虽然 Python 代码默认使用了 UTF- 8 作为编码,但并不意味着外部输入的数据也是 UTF- 8 编码的,如果这些外部的数据没有指定编码,那么在处理他们是,你就要格外小心了。比如你调用 API 获取数据,正常是用 UTF- 8 去解码,没有问题,但如果突然 API 给你返回这样的数据:

>>> data = b"\xbc cup of flour"
>>> data.decode("utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbc in position 0: invalid start byte

这地方抛出了 UnicodeDecodeError 错误,仔细检查,发现其实数据的编码是 Latin-1。

>>> data.decode("latin-1")
'¼ cup of flour'

如果你对字符串的编码不确定,可以使用 chardet 库来检查字符串编码。

总结

在本文中你已经了解了编码的详细原理,相信你在以后的编程过程中,再遇到编码错误,相信你能比较从容的解决了。

翻译自: 原文地址
点击获取我的更多文章
关注我,获取更多文章。

正文完
 0