美文网首页Python
关于Python编码这一篇文章就够了

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

作者: IT派森 | 来源:发表于2019-06-10 23:19 被阅读22次

    概述

    在使用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库来检查字符串编码。

    总结

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

    大家在学python的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的Python学习扣qun:784758214,这里是python学习者聚集地!!同时,自己是一名高级python开发工程师,从基础的python脚本到web开发、爬虫、django、数据挖掘等,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!每日分享一些学习的方法和需要注意的小细节

    相关文章

      网友评论

        本文标题:关于Python编码这一篇文章就够了

        本文链接:https://www.haomeiwen.com/subject/okhoxctx.html