流畅的Python(四)- 文本和字节序列

一、本章核心要义

Python3 明确区分了人类可读的文本字符串和原始的字节序列。本章主要讨论Unicode字符串、二进制序列,以及两者之间转换时使用的编码。

二、代码示例

1、Unicode字符

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/20 20:10
# @Author  : Maple
# @File    : 01-Unicode字符.py
# @Software: PyCharm
import dis

if __name__ == '__main__':

    """字符的最佳定义是Unicode字符,其包含两个不同的组成部分
       1. 字符的`标识`,即码位.比如字母A的码位是U+0041
       2. 字符的`具体字节表述`,其与不同的编码方式有关,比如字母A(U+0041)在UTF-8编码中,对应的字节表述为'\x41';但是在UTF-16LE编码中,对应的字节表述为'\41\x00'
    """
    s = 'é'
    b = s.encode('utf-8')
    # 打印é在utf-8编码下的字节表述
    print(b) # '\xc3\xa9'

    # 打印é在utf-16LE编码下的字节表述
    b2 = s.encode('utf-16LE')
    print(b2) # b'\xe9\x00'

    # utf-8编码下的字节 解码为字符
    s1 = b.decode('utf-8')
    print(s1) #é

    # utf-16LE编码下的字节 解码为字符
    s2 = b2.decode('utf-16LE')
    print(s2) #é

2、字节序列

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/20 20:28
# @Author  : Maple
# @File    : 02-字节序列.py
# @Software: PyCharm


if __name__ == '__main__':

    """Python内置了两种基本的二进制序列类型:bytes和bytearray
    """
    # 1. 通过构造器方式创建bytes对象
    b = bytes('café\n}',encoding='utf8')

    # 打印bytes对象,不同的元素会有三种不同的显示方式
    """
    1. 对于ASCII范围内的字节,使用ASCII字符本身,如下示例中的caf
    2. 制表符、换行符、回车符和\对应的字节,使用转义序列\t,\n,\r和\\
    3. 其它字节的值,使用十六进制转义序列,如下示例中的\xc3\xa9
    """
    print(b) # b'caf\xc3\xa9\n}'

    # 截取bytes对象中的某个元素,返回的是[0-255]之间的整数

    for i in range(len(b)):
        """打印结果
        99 对应字符c的ascii码
        97 对应字符a的ascii码
        102 对应字符f的ascii码
        195 对应xc3(x是十六机制标识,c3 = 16 * 12 + 3)的十进制整数值
        169 对应xa9(x是十六机制标识,a3 = 16 * 10 + 9)的十进制整数值
        10  对应换行符\n的ascii码
        125 对应字符}的ascii码
        
        关于为何返回的是[0-255]之间的整数?
          --两位十六进制(x**)的最大数值是ff-->对应十进制值是255,所以最终返回的整数都会在[0-255]之间
        """
        print(b[i])

    print('----------------')

    # 通过切片方式截取,返回的是byte对象
    print(b[:1])# b'c'

    # 2. 通过构造器方式创建 bytearray 对象
    b2= bytearray('café\n}',encoding='utf-8')
    print(b2) # bytearray(b'caf\xc3\xa9\n}')

    # 截取 bytearray 对象中的某个元素,返回的是[0-255]之间的整数
    for i in range(len(b2)):
        """打印结果:同上
        """
        print(b2[i])

    # 通过切片方式截取,返回的是bytearray对象
    print(b2[:1]) # bytearray(b'c')

3、struct模块

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 8:42
# @Author  : Maple
# @File    : 03-struct模块.py
# @Software: PyCharm

if __name__ == '__main__':

    """
    主要实现将Python中的数据(数值、字符等)以指定的格式(fmt)进行打包(pack)
    以及 将二进制序列(bytes) 以指定的格式(fmt)进行解包(unpack)
    """
    from struct import *

    #1. 对整数1,2,3 按照指定的格式进行打包,转换成对应的bytes序列形式
    # h是指short类型,默认大小2个字节,所以1会被打包成\x01\x00;2会被打包成\x02\x00\
    # l是指long类型,默认大小3个字节,所以4会被打包成x03\x00\x00\x00
    p1 = pack('hhl',1,2,3)
    print(p1) # b'\x01\x00\x02\x00\x03\x00\x00\x00'

    #2. 对p1进行解包
    u1 = unpack('hhl',p1)
    print(u1) # (1, 2, 3)

    #3. 解包的字段可通过将他们赋值给变量或将结果包装成一个具名元组来命名(关于具名元组可参考第二章`序列类型`的元组部分)
    record = b'raymond   \x32\x12\x08\x01\x08'
    # < 表示小字字节;10s表示10位字节串,H表示unsigned short,默认2个字节,b表示signed char,默认1个字节
    name,serialnum,school,gradelevel = unpack('<10sHHb',record)

    from collections import namedtuple
    Student = namedtuple('Student','name,serialnum,school,gradelevel')
    # 创建具名元组实例
    s = Student._make(unpack('<10sHHb',record))
    print(s) # Student(name=b'raymond   ', serialnum=4658, school=264, gradelevel=8)

4、基本编码器

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 9:06
# @Author  : Maple
# @File    : 04-基本编码器.py
# @Software: PyCharm


if __name__ == '__main__':

    # 使用3个编码器编码字符串'é',会得到不同的结果
    # 目前Web中最常见的编码是utf-8
    for codec in ['latin_1','utf-8','utf-16']:
        """打印结果:
        latin_1	b'\xe9'
        utf-8	b'\xc3\xa9'
        utf-16	b'\xff\xfe\xe9\x00'"""
        print(codec,'é'.encode(codec),sep='\t')

5、编码和解码错误

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 9:10
# @Author  : Maple
# @File    : 05-编码和解码错误.py
# @Software: PyCharm

if __name__ == '__main__':

    # 1. 编码错误
    s = '2024ā'
    # b1 = s.encode('cp1252')
    # UnicodeEncodeError: 'charmap' codec can't encode character '\u0101' in position 0: character maps to 
    # 'cp1252'编码器无法 编码字符ā,因此会报 UnicodeEncodeError 错误
    # print(b1)

    # 2.处理编码错误
    ## 2-1 忽略错误
    b2 = s.encode('cp1252',errors='ignore')
    print(b2) # b'2024'

    ## 2-2 替换无法解析字符为?
    b3 = s.encode('cp1252', errors='replace')
    print(b3) # b'2024?'

    ## 2-3 替换无法解析字符为xml实体
    b4 = s.encode('cp1252', errors='xmlcharrefreplace')
    print(b4) # b'2024ā'

    # 3.解码错误
    b5 = s.encode('utf-8')
    print(b5)  # b'\xc4\x81'

    # 'charmap' codec can't decode byte 0x81 in position 1: character maps to 
    # 'cp1252'解码器无法解析0x81,因此会报 UnicodeDecodeError错误
    # print(b5.decode('cp1252'))

    # 4.处理解码错误
    ## 3-1 忽略
    d1 = b5.decode('cp1252',errors='ignore')
    print(d1) # 2024Ä

    ## 替换
    d2 = b5.decode('cp1252',errors='replace')
    print(d2) # 2024�

6、BOM:有用的鬼符

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 9:36
# @Author  : Maple
# @File    : 06-有用的鬼符.py
# @Software: PyCharm


if __name__ == '__main__':
    """
    1. 对于一个字符占多个字节的场景,utf-16编码的小字节序和大字节序 编码结果会不同
    2. utf-8编码不会做该区分
    """

    # 1. 小字节序
    b1 = 'Maplé2'.encode('utf-16')
    # 前面的b'\xff\xfe'是BOM,即字节序标记,指明编码时使用Intel CPU的小字节序
    # 小字节序中 各个码位的最低有效位在前面,比如2的字节序列是2\x00,有效的2在前面;如果是大字节序,则相反
    print(b1) # b'\xff\xfeM\x00a\x00p\x00l\x00\xe9\x002\x00'

    # 2.显式指定字节序(注意以上即这里针对的都是utf-16编码)
    # 显式指定之后,都不会生成BOM
    ## 2-1 显式指定小字节序,对于数值2:编码为2\x00,有效的2在前面
    b2 = 'Maplé2'.encode('utf-16le')
    print(b2) # b'M\x00a\x00p\x00l\x00\xe9\x002\x00'

    ## 2-2 显式指定大字节序,对于数值2:编码为\x002,有效的2在后面
    b3 = 'Maplé2'.encode('utf-16be')
    print(b3) #b'\x00M\x00a\x00p\x00l\x00\xe9\x002'

7、处理文本文件

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 9:55
# @Author  : Maple
# @File    : 07-处理文本文件.py
# @Software: PyCharm

if __name__ == '__main__':

    # 1.写入文件:指定编码方式为utf-8
    with open('data/cafe.txt','w',encoding='utf-8') as f:
        f.write('café')

    # 2.读取文件
    ##2-1 不指定编码方式,Python假定使用系统默认的编码Windows 1252
    with open('data/cafe.txt','r') as f:
        line = f.readline()
        print(line) # caf茅

    ##2-2 指定编码方式,正确读取
    with open('data/cafe.txt','r',encoding='utf-8') as f:
        line = f.readline()
        print(line) # café

8、字符串规范化

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/1/21 10:00
# @Author  : Maple
# @File    : 08-字符串规范化.py
# @Software: PyCharm

if __name__ == '__main__':

    s1= 'café'
    print(s1) # café
    s2 = 'cafe\u0301'
    print(s2) # café

    # 1. 在Unicode标准中,é和e\u0301这样的序列叫做'标准等价物',但Python却会将其判为不相等
    print(s1 == s2 ) #False

    print('--------------------')

    # 2. 规范化
    from unicodedata import normalize
    ##2-1  NFC: 使用最少的码位构成等价的字符串
    s1_nfc = normalize('NFC',s1)
    print(len(s1_nfc)) #4
    s2_nfc = normalize('NFC',s2)
    print(len(s2_nfc)) #4
    print(s1_nfc == s2_nfc) # True

    print('--------------------')

    ##2-2  NFD: 将组合字符拆分成基字符和单独的组合字符,比如将é拆分成e和\u0301
    s1_nfd = normalize('NFD', s1)
    print(len(s1_nfd))  # 5
    s2_nfd = normalize('NFD', s2)
    print(len(s2_nfd))  # 5
    print(s1_nfd == s2_nfd)  # True

你可能感兴趣的:(Python,python)