在使用Python或者其余的编程语言,都会多多少少遇到编码错误,处理起来很是痛苦。在Stack Overflow和其余的编程问答网站上,UnicodeDecodeError和UnicodeEncodeError也常常被说起。本篇教程但愿能帮你认识Python编码,并可以从容的处理编码问题。html
本教程提到的编码知识并不限定在Python,其余语言也大同小异,但咱们依然会以Python为主,来演示和讲解编码知识。python
经过该教程,你将学习到以下的知识:git
如今的编码规则已经有好多了,最简单、最基本是的ASCII编码,只要是你学过计算机相关的课程,你就应该多少了解一点ASCII编码,他是最小也是最适合了解字符编码原理的编码规则。具体以下:编程
那么,字符编码的定义究竟是什么了?它是一种将字符(如字母,标点符号,符号,空格和控制字符)转换为整数并最终转换为bit进行存储的方法。 每一个字符均可以编码为惟一的bit序列。 若是你对bit的概念不了解,请不要担忧,咱们后面会介绍。windows
ASCII码的字符被分为以下几组:app
ASCII表一共包括128个字符,若是你想了解整个ASCII表,这里有编程语言
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是计算机内部存储单位,只有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采用的是8bit来存储字符(只使用7位,剩下的1位二进制为0),因此,ASCII最多存储128个字符,这有个简单的公式,计算存储字符的bit数量与存储字符总数的关系:2的n次方,n表示bit数量。例如:
咱们能够写个简单的代码,来计算一下,指定字符数量,至少须要多少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还支持如下几个方式:
你可能要问,为何有了十进制,还要支持这么多其余进制的数字了?这个取决你的业务场景和操做系统,在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
正如您所看到的,ASCII的问题在于它不是一个足够大的字符集来容纳世界上的语言,方言,符号和字形。 (这对于英语来讲甚至都不够大。)Unicode从根本上起到与ASCII相同的做用,可是Unicode拥有更大的存储空间,具备1,114,112个可能的字符,可以彻底包含世界上全部的语言。事实上,ASCII是Unicode的完美子集。 Unicode表中的前128个字符与您合理指望的ASCII字符彻底对应。叉车维修
Unicode自己不是编码,可是有不少遵循Unicode编码规范编码,后面讲到的UTF-8就是其中一个。
Unicode是一种抽象编码标准,而不是编码。这就是UTF-8和其余编码方案发挥做用的地方。 Unicode标准(字符到代码点的映射)从其单个字符集定义了几种不一样的编码。UTF-8及其较少使用的表兄弟UTF-16和UTF-32是用于将Unicode字符表示为每一个字符一个或多个字节的二进制数据的编码格式。咱们稍后将讨论UTF-16和UTF-32,但到目前为止,UTF-8占据了最大份额。
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'
默认状况下,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()的一个微妙但重要的特性:
咱们来聊聊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内置了不少与编码相关的函数:
能够分红如下几组:
目前,咱们讲了4中编码:
还有其余不少编码,好比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库来检查字符串编码。
在本文中你已经了解了编码的详细原理,相信你在之后的编程过程当中,再遇到编码错误,相信你能比较从容的解决了。