美文网首页
血案后对python3.7最新字节字符编码解码知识整理

血案后对python3.7最新字节字符编码解码知识整理

作者: 寻找无双丶 | 来源:发表于2019-08-08 10:49 被阅读0次

    之前因为工作上面的需要,查了好多字符串字节编码解码方面的资料,结果发现鱼龙混杂。本身由于Python3与Python2在这方面的改动很大,再加上从Python3开始,伴随着版本的迭代,一些方法也有了变化,很多以前的转换方法都不能用了,整个过程被折磨的脑壳疼,在此从自己本身的理解上面做个总结,以后方便自己来查阅。

    1. 先从概念理解开始

    某大佬云:人类使用文本,计算机使用字节序列

    我们先可以看一下从ASCII到Unicode的发展历史,然后理解下面的概念。

    • bit:二进制位, 是计算机内部数据储存的最小单位,11010100是一个8位二进制数
    • byte:字节,是计算机中数据处理的基本单位,计算机中以字节为单位存储和解释信息,规定一个字节由八个二进制位构成,即1个字节等于8个比特(1Byte=8bit)。八位二进制数最小为00000000,最大为11111111;通常1个字节可以存入一个ASCII码,2个字节可以存放一个汉字国标码
    • Unicode:Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求
    • 字符:“字符”的最佳定义是Unicode字符,字符的具体表述取决于所用的编码
    • 字符串:一个字符串就是一个字符序列(多个字符组成)
    • 码位:字符的标识,是0~1114111(这个数字记着,下面有用)的数字,在Unicode中以4-6个十六进制数字表示,而且加前缀“U+”,比如字母A的码位就是U+0041,欧元符号€的码位就是U+20AC
    • 编码:把码位转换成字节序列的过程就是编码
    • 解码:把字节序序列转换成码位的过程

    简单看一个书本上例子当下酒菜:

    >>> s = 'café'
    >>> len(s) # ➊
    4
    >>> b = s.encode('utf8') # ➋
    >>> b
    b'caf\xc3\xa9' # ➌
    >>> len(b) # ➍
    5
    >>> b.decode('utf8') # ➎
    'café'
    

    ❶ 'café' 字符串有 4 个 Unicode 字符。
    ❷ 使用 UTF-8 把 str 对象编码成 bytes 对象。
    ❸ bytes 字面量以 b 开头。
    ❹ 字节序列 b 有 5 个字节(在 UTF-8 中,“é”的码位编码成两个字节)。
    ❺ 使用 UTF-8 把 bytes 对象解码成 str 对象

    如果想帮助自己记住 .decode() 和 .encode() 的区别,可以把字节序列想成晦涩难懂的机器磁芯转储,把 Unicode 字符串想成“人类可读”的文本。那么,把字节序列变成人类可读的文本字符串就是解码,而把字符串变成用于存储或传输的字节序列就是编码。

    2. 几个常会看到的函数

    数字转换

    bin(),oct(),int(),hex(),这些函数就不说了,数字转换手算算也是非常简单的。但是我们要知道Python中各种进制数据的表示,以十进制的23为例,二进制前缀为0b,0b10111,八进制前缀为0o,0o27,十六进制前缀为0x,0x17.

    一个在转换进制的同时高位补零的小技巧:

    >>> bin(2)
    0b10'
    >>> '{:08b}'.format(2)
    '00000010'
    >>> '{:8b}'.format()
    '      10'
    # 输出的都是字符串
    >>> int('00000010',2)
    2
    

    不起眼的主角

    chr(i)返回 Unicode 码位为整数 i ( 0 <= i <= 0x10ffff)的字符的字符串格式。例如,chr(97) 返回字符串 'a',chr(8364) 返回字符串 '€'。这是 ord() 的逆函数

    ord(c)对表示单个 Unicode 字符的字符串,返回代表它 Unicode 码点的整数。例如 ord('a') 返回整数 97, ord('€') (欧元符合)返回 8364 。这是 chr() 的逆函数。

    所以上面chr函数里面i的范围最大为0x10ffff,转换成十进制就是码位概念里字符标识数字的1114111,欧元符号转换也是同理,0x20AC就是8364。

    3. 计算机所能理解的字节

    Python中的字节对象

    操作二进制数据的核心内置类型是 bytes 和 bytearray。 它们由 memoryview 提供支持,该对象使用 缓冲区协议 来访问其他二进制对象所在内存,不需要创建对象的副本。
    bytearray():返回一个新的 bytes 数组。 bytearray 类是一个可变序列,包含范围为 0 <= x < 256 的整数。它有可变序列大部分常见的方法
    bytes():返回一个新的“bytes”对象, 是一个不可变序列,包含范围为 0 <= x < 256 的整数,bytes 是 bytearray 的不可变版本 - 它有其中不改变序列的方法和相同的索引、切片操作。
    Python中bytes 字面值中只允许 ASCII 字符(无论源代码声明的编码为何)。 任何超出 127 的二进制值必须使用相应的转义序列形式加入 bytes 字面值。

    bytearray和bytes不一样的地方在于,bytearray是可变的,它们的关系就相当于list与tuple

    还是来看书上例子

    >>> cafe = bytes('café', encoding='utf_8') ➊
    >>> cafe
    b'caf\xc3\xa9'
    >>> cafe[0] ➋
    99
    >>> cafe[:1] ➌
    b'c'
    >>> cafe_arr = bytearray(cafe)
    >>> cafe_arr ➍
    bytearray(b'caf\xc3\xa9')
    >>> cafe_arr[-1:] ➎
    bytearray(b'\xa9')
    

    ❶ bytes 对象可以从 str 对象使用给定的编码构建。
    ❷ 各个元素是 range(256) 内的整数。
    ❸ bytes 对象的切片还是 bytes 对象,即使是只有一个字节的切片。
    ❹ bytearray 对象没有字面量句法,而是以 bytearray() 和字节序列字面量参数的形式
    显示。
    ❺ bytearray 对象的切片还是 bytearray 对象。

    my_bytes[0] 获取的是一个整数,而 my_bytes[:1] 返回的是一个长度为 1的 bytes 对象——这一点应该不会让人意外。s[0] == s[:1] 只对 str 这个序列类型成立。不过,str 类型的这个行为十分罕见。对其他各个序列类型来说,s[i] 返回一个元素,而 s[i:i+1] 返回一个相同类型的序列,里面是 s[i] 元素。

    简单分析

    上面的例子中,我们看到Python中字节用的是b'caf\xc3\xa9'来表示,通过一个前缀b来表示字节,也是为了让人更明白,cafe[0]得到的是99,我们打开ascii码表,或者用ord('c')知道了,99代表的就是'c'。从内部来看,这个字节在计算机中的真正存在方式是用01100011来表示,后面的af类似,而é在utf8编码中要用两个字节表示,而且这两个字节都大于127,所有只能使用十六进制转义。
    那为什么要用十六进制呢,而不是二进制来表明字节?

    由于字节(byte)在计算机内部出现的频率较高,如果可以使用一种简洁的方式将它的内在含义准确表达出来,将会给我们带来很多方便。选择十六进制,是因为8位二进制的数字可以方便的转换为2个十六进制的数字。一个字节能且只能由一对十六进制来表示,比如10110110可以表示为B6。如果使用4进制的话则需要使用4个数字来表示一个字节,不够简洁;使用8进制的话,最靠左的8进制数是由2位二进制数字来表示的,相比于使用16进制有些美中不足。

    说到底,Python给我们返回来的字节形式还是“给人看的”,让我们以一种看字符的方式来看字节。

    字节的两个好用方法来应对十六进制

    fromhex(string) (与binascii.b2a_hex(string)类似):此 bytes 类方法返回一个解码给定字符串的 bytes 对象。 字符串必须由表示每个字节的两个十六进制数码构成,其中的 ASCII 空白符会被忽略
    hex(h)(与binascii.a2b_hex(string)类似)):返回一个字符串对象,该对象包含实例中每个字节的两个十六进制数字。

    >>> cafe = bytes('café', encoding='utf_8')
    >>> cafe
    b'caf\xc3\xa9'
    >>> cafe.hex()
    '636166c3a9'
    >>>bytes.fromhex('63 61 66 c3 a9')
    b'caf\xc3\xa9'
    # 另一种写方法,用的不多,一些手册或者其他语言里的十六进制数据都是没有0x前缀,还要手动补,不靠谱
    >>> bytes([0x63,0x61,0x66,0xc3,0xa9])
    b'caf\xc3\xa9'
    

    查看下bytes用法

    Init signature: bytes(self, /, *args, **kwargs)
    Docstring:
    bytes(iterable_of_ints) -> bytes
    bytes(string, encoding[, errors]) -> bytes
    bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
    bytes(int) -> bytes object of size given by the parameter initialized with null bytes
    bytes() -> empty bytes object

    好了,这下心里的结都理清楚了,特别是要给下位机发送一段十六进制的数字的组合,就可以这么转换了

    4. 结构体与内存视图

    struct 模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的bytes、bytearray 和 memoryview 对象。

    虽然网上找的时候好多遇到了struct模块,但这里不多说,留下两个链接内存试图struct模块官方手册

    5. 编码与解码

    Python 自带了超过 100 种编解码器(codec, encoder/decoder),用于在文本和字节之间相互转换。每个编解码器都有一个名称,如 'utf_8',而且经常有几个别名,如'utf8'、'utf-8' 和 'U8'。这些名称可以传给open()、str.encode()、bytes.decode() 等函数的 encoding 参数。

    终于到了编码与解码,其实万剑归一,那些科技大佬们在混沌的一片编码方式中劈开了一斧,创建了utf8,我们只要用它就行了。看下面一个例子。

    >>>  k = '欢'.encode('utf8')
    >>>  k
    b'\xe6\xac\xa2' # ➊
    >>> print(bin(k[0])+'    '+bin(k[1])+'    '+bin(k[2]))
    0b11100110    0b10101100    0b10100010  # ➋
    

    ❶ 汉字‘欢’通过utf8编码程三个字节
    ❷ 打印这三个字节的二进制

    然后再来看Unicode字符代码与UTF-8编码的对应关系,发现与下面的第三条是对应的,所以编码也不是乱编的,你随便写一个字节再解码都是容易报错的,百分之九十九,除非你运气好。

    0000 0000-0000 007F | 0xxxxxxx
    
    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
    
    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
    
    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    

    6.尾声

    在整理以上内容的过程中,又收获了新的知识,头脑里对这一块又清晰多了。字节字符编码解码无论在哪门语言中都是快让人头疼难啃的骨头,其实不在于Python中语法是怎样的,而是对概念的理解,人类对编码的越加完善,架设了这么一条人与计算机交流的的桥梁。科技的美妙在于此~

    相关文章

      网友评论

          本文标题:血案后对python3.7最新字节字符编码解码知识整理

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