读书笔记:《流畅的Python》第4章 文本和字节序列

本文详细探讨了Python中处理Unicode字符串和字节序列的方法,包括Unicode字符串的编码与解码、字节序列的创建与操作、字符与码位的关系、编码错误的处理、文本文件的最佳实践以及Unicode文本的规范化比较。还介绍了如何避免默认编码陷阱,使用不同的编解码器以及处理非UTF编码文件时的策略。
摘要由CSDN通过智能技术生成
# 第四章 文本和字节序列

"""内容提要:
    1.Unicode字符串
    2.二进制序列
    3.在二者之间转换使用的编码
    4.字符/码位/字节表述
    5.bytes/bytearray/memoryview等二进制序列的独特特性
    6.全部Unicode和陈旧字符集的编解码器
    7.避免和处理编码错误
    8.处理文本文件的最佳实践
    9.默认编码的陷阱和标准I/O的问题
    10.规范化Unicode文本,进行安全的比较
    11.规范化/大小写折叠/暴力移除音调符号的实用函数
    12.使用locate模块和PyUCA库正确第排序Unicode文本
    13.Unicode数据库中的字符元数据
    14.能处理字符串和字节序列的双模式API
    """

# 4.1 字符问题
"""
字符的标识:即码位,是0~1114111的数字(十进制),在Unicode标准中以4~6个十六进制数字表示
加前缀'U+'如
    A : U+0041
    欧元符号: U+20AC
    高音谱号:U+D11E
字符的表述:取决于具体的编码
    编码是在码位和字节序列之间转化时使用的算法
    在utf-8中 A 编码为单个字节\x41
    utf-16LE中  A 编码为两个字节\x41\x00
    编码:把码位转换为字节序列
    解码:把字节序列转换为码位"""

# 编码和解码
# s = 'café'
# print(len(s)) # 'café'有4个Unicode字符
# b = s.encode('utf8') # 编码
# print(b) # b'caf\xc3\xa9' byte的字面量以b开头
# print(len(b)) # 字节序列有5个字节(在utf8中é的码位编码成2个字节)
# print(b.decode('utf8'))  # 解码


# 4.2 字节概要
"""
字节类型:
    1.bytes 不可变
    2.bytearray 可变
    bytes和bytearray对象的各个元素是介于0~255(含)之间的整数
    二进制序列的切片始终是同一类型的二进制序列
    二进制序列的字面量表示:
        1.可打印的ASCII范围内的字节用ASCII字符表示
        2.制表符 换行符 回车符和\对应的字节用转义序列表示 \t \n \r \\
        3.其他字节的值,使用时十六进制转义序列 \x00 表示空字节
    除了格式化方法(format和format_map)和
    几个处理Unicode数据的方法
        casefold isdecimal isdentifier isnumeric isprintable encode外
    str类型的其他方法都支持bytes和bytearray
    re模块也支持处理二进制序列
    """

# 使用数组中的原始数据初始化bytes对象
# 例 包含5个字节的bytes和bytearray对象

"""cafe = bytes('café',encoding='utf8')  # bytes对象从str使用指定的编码构建
print(cafe)  # b'caf\xc3\xa9'
print(cafe[0])  # 99  # 各个元素是0~255的整数
print(cafe[:1])  # b'c'  # 切片还是bytes对象,即使是只有一个字节的切片

cafe_arr = bytearray(cafe)
print(cafe_arr)  # bytearray(b'caf\xc3\xa9')  bytearray没有字面量表示
print(cafe_arr[-1:])  # bytearray(b'\xa9') bytearray切片还是bytearray"""

# fromhex:解析十六进制数字对(数字对之间的空格是可选的),构建二进制序列
# print(bytes.fromhex('31 4B CE A9'))

"""
构建bytes和bytearray实例海可以调用各自的构造方法,传入以下参数:
1.一个str对象和一个encoding关键紫参数
2.一个可迭代对象,提供0~255之间的整数
3.一个整数,使用空字节创建对应长度的二进制序列
4.一个实现了缓冲协议的对象
    如: bytes,bytearray,memoryview,array.array
"""
# 例
# 4-3使用数组中的原始数据初始化bytes对象

"""import array
numbers = array.array('h',[-2,-1,0,1,2])  # 指定类型代码h,创建一个短整型数组
octets = bytes(numbers)  # 保存组成numbers的字节序列的副本
print(octets)  # b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00'是表示那五个短整数的10个字节"""

# 结构题和内存视图
"""struct模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组
示例:memoryview和struct处理gif图像.py"""


#  4.3 基本的编解码器

# 4-5使用3个编解码器编码字符串
# for codec in ['latin_1','utf-8','utf-16']:
#     print(codec,'El Niño'.encode(codec),sep='\t')
# print(b'El Ni\xc3\xb1o'.decode('utf8'))

"""
latin1 : 一种重要的编码,是其他编码的基础
cp1252 : Microsoft制定的latin1的超集
cp437 : IBM PC最初的字符集
gb2312 : 编码简体中文的陈旧字符集
utf-8 : 目前web中最常用的编码,与ASCII兼容
utf-16le : 16位编码方案的一种
"""

# 4.4了解编解码问题

# 4.4.1处理UnicodeEncodeError

"""多数非UTF编码只能处理Unicode的一个子集
把文本转换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出UnicodeEncodeError异常
除非把errors参数传给编码方法或者函数,对错误进行特殊处理"""

# 4-6 编码成字节序列:成功和错误处理
# city = b'S\xc3\xa3o paulo'.decode(encoding='utf8')
# print(city)

"""city = 'São paulo'
print(city.encode('utf8'))
print(city.encode('utf16'))  # b'\xff\xfeS\x00\xe3\x00o\x00 \x00p\x00a\x00u\x00l\x00o\x00'
print(city.encode('iso8859_1'))  # b'S\xe3o paulo'
# print(city.encode('cp437'))  # 报错 UnicodeEncodeError
print(city.encode('cp437',errors='ignore'))
# b'So paulo'  悄无身息地跳过无法处理的字符
print(city.encode('cp437',errors='replace'))
# b'S?o paulo' 把无法编码的字符替换成'?'
print(city.encode('cp437',errors='xmlcharrefreplace')) 
# b'São paulo' 把无法编码的字符替换成xmlsh实体"""

# 4.4.2 处理UnicodeDecodeError
"""把二进制序列转换成文本时,遇到无法转换的字节序列时抛出UnicodeDecodeError"""

# 4-7 解码字节序列:成功和错误处理
"""
octets = b'Montr\xe9al'
print(octets.decode('cp1252'))  # Montréal 正常解码
# iso8859_7用于编码希腊文
print(octets.decode('iso8859_7'))  # Montrιal 没有抛出错误
# koi8_r用于编码俄文
print(octets.decode('koi8_r'))  # MontrИal
# print(octets.decode('utf8'))  # 报错 不是有效的utf8字节序列
print(octets.decode('utf8',errors='replace'))  # Montr�al
"""

# 4.4.3使用预期之外的编码加载模块时抛出SyntaxError
"""
python3的源码默认使用utf8编码
python2使用的是ASCII
在文件顶部添加 
    # coding:cp1252
    可以指定编码方式
"""

# 4.4.4如何找出字节序列的编码
"""Chardet能识别所支持的30种编码"""

#4.4.5 BOM:有用的鬼符
"""BOM(byte-order mark)指的是字节序标记
小字节序:各个码位的最低有效字节在前面
    'E' 的 码位时U+0045(十进制69),在字节偏移的第二位和第三位编码为69和0
大字节序:
    'E' 的编码为0和69"""

# u16 = 'São paulo'
# print(u16.encode('utf16')) # b'\xff\xfeS\x00\xe3\x00o\x00 \x00p\x00a\x00u\x00l\x00o\x00'
# # 即这里的b'\xff\xfe,指明编码时使用intel CPU的小字节序
#
# print(list(u16))


# 4.5处理文本文件
"""Unicode三明治
    1.对于输入来说,尽可能早地把输入的字节序列解码成字符串
    2.三明治中的肉片时程序的业务逻辑,在这里只能处理字符串对象,在其他处理过程钟一定不能编码或解码
    3.对于输出来说,要尽可能晚地把字符串编码成字节序列"""

# open('cafe.txt','w',encoding='utf8').write('café')
# open('cafe.txt').read()  # 'caf茅'  本机

# 4.6 为了正确比较而规范化Unicode字符串

# s1 = 'café'
# s2 = 'cafe\u0301'
# print(s1, s2)
# # >>>café café
# print(len(s1), len(s2))
# # >>>4 5
# print(s1 == s2)  # False

"""é和e\u0301在Unicode标准中叫做标准等价物
    但python看到的是不同的码位序列,因此判定不相等
    这个问题可以使用Unicodedata.normalize()提供的Unicode规范化来解决
    Unicodedata.normalize()
        第一个参数:
            'NFC' 使用最少的码位来构成等价字符串
            'NFD'  把组合字符分解成基字符和单独的组合字符
            'NFKC'  K表示兼容性 是较为严格的规范化形式 
                μ(希腊字母) U+03BC 和 微符号 μ U+00B5
            'NFKD' 'NFKC''NFKD'可能会在规范化的过程中损失或者曲解意思
                不能用于持久存贮
            """
# normalize函数示例
"""from unicodedata import normalize
s1 = 'café' # 作为组合字符
s2 = 'cafe\u0301' # 分解为基字符和重音符
print(len(s1), len(s2))
# >>>4 5
print(len(normalize('NFC', s1)), len(normalize('NFC', s2)))
# >>> 4 4
print(len(normalize('NFD', s1)), len(normalize('NFD', s2)))
# >>> 5 5
print(normalize('NFC',s1) ==normalize('NFC',s2))  # True
print(normalize('NFD',s1) ==normalize('NFD',s2))  # True"""

# NFC的特殊情况
"""from unicodedata import normalize ,name
ohm = '\u2126'
print(ohm)
print(name(ohm))  # OHM SIGN 电阻单位欧姆Ω
ohm_c = normalize('NFC',ohm)
print(name(ohm_c))  # GREEK CAPITAL LETTER OMEGA 希腊小写字母omega
print(ohm == ohm_c) # False
print(normalize('NFC', ohm) == normalize('NFC', ohm_c))# True"""

# NFKC示例
"""from unicodedata import normalize ,name
half = '\u00BD'
print(half)
print(normalize('NFKC', half))  # 1⁄2 分解成3个字符
four_squared = '4²'
print(normalize('NFKC', four_squared))  # 42  分解成4 2

micro = '\u00B5'
micro_kc = normalize('NFKC',micro)
print(micro,micro_kc)  # μ μ

print(ord(micro), ord(micro_kc)) # 181 956
print(name(micro), name(micro_kc))
# >>>MICRO SIGN          GREEK SMALL LETTER MU"""

# 4.6.1大小写折叠
# 把所有文本变成小写,再做其他转换  这个功能由str.casefold()支持
# 一般情况下  str.casefold()和lower()结果一样
#     特殊情况:
#         μ变成小写的希腊字母μ,但在大多数字体显示效果一样
#         德语的Eszett会变成ss

# 4.6.2规范化文本实用函数
# normeq.py

# 4.6.3 极端规划化 去掉变音符号
# sanitize.py

# 4.7 Unicode文本排序
# 使用locale.strxfrm函数做排序的键
# import locale
# locale.setlocale(locale.LC_COLLATE,'pt_BR.UTF-8')
# locale.Error: unsupported locale setting 操作系统不支持
# 在sorted中使用 key= locale.strxfrm 就可以使用区域设置中的区域规则来作为排序的依据

# 4.8 Unicode数据库
"""Unicode标准提供了一个完整的数据库:
    包括码位和字符名称之间的映射
    各个字符的元数据
    字符之间的关系"""

# 元数据示例
"""import unicodedata
import re
re_digit = re.compile(r'\d')
sample = '1\xbc\xb2\u0969\u136b\u216b\u2466\u2480\u3285'

for char in sample:
    print('U+%04x'%ord(char),
    char.center(6),
    're_dig' if re_digit.match(char) else '-',
    'isdig' if char.isdigit() else '-',
    'isnum' if char.isnumeric() else '-',
    format(unicodedata.numeric(char),'5.2f'),
    unicodedata.name(char),
    sep = '\t')"""

# 4.9 支持字符串和字节序列的双模式API
# 提供的函数能接受字符串或者字节序列为参数,然后根据类型进行特殊处理
# re和os模块有这样的函数

# 4.9.1正则表达式中的字符串和字节序列
# ramanujan.py

# 4.9.2 os函数中的字符串和字节序列

探索编码默认值 default_encoding.py

# 探索编码默认值
import sys,locale
expressions = """
    locale.getpreferredencoding()
    type(my_file)
    my_file.encoding
    sys.stdout.isatty()
    sys.stdout.encoding
    sys.stdin.isatty()
    sys.stdin.encoding
    sys.stderr.isatty()
    sys.stderr.encoding
    sys.getdefaultencoding()
    sys.getfilesystemencoding()"""

my_file = open('dummy','w')
for expression in expressions.split():
    value = eval(expression)
    print(expression.rjust(30),'->',repr(value))

"""结果:
 locale.getpreferredencoding() -> 'cp936'  # 打开文件的默认编码
                 type(my_file) -> <class '_io.TextIOWrapper'>
              my_file.encoding -> 'cp936'
           sys.stdout.isatty() -> False
           sys.stdout.encoding -> 'utf-8'
            sys.stdin.isatty() -> False
            sys.stdin.encoding -> 'utf-8'
           sys.stderr.isatty() -> False
           sys.stderr.encoding -> 'utf-8'
      sys.getdefaultencoding() -> 'utf-8'
   sys.getfilesystemencoding() -> 'utf-8'"""

# 结论:不要依赖默认值
4-4使用memoryview和struct查看gif图像的首部
# 4-4使用memoryview和struct查看gif图像的首部

import struct
fmt = '<3s3sHH' # '<' 小字节序,'3s3s'两个3字节序列,'HH'两个16位二进制整数
with open('sda.gif','rb') as fp:
    img = memoryview(fp.read())  # 使用内存中的文件创建了一个memoryview对象

header = img[:10]  # memoryview对象的一个切片
tmp = bytes(header)  # 转换成字节序列以显示
print(header)
print(tmp)

print(struct.unpack(fmt,header))  # 拆包memoryview对象,得到一个元组,包含类型,版本,宽度和高度

del header
del img  # 删除引用释放memoryview实例所占用的内存
# 4.6.2规范化文本实用函数
# normeq.py
# Utility Function for normalized Unicode string C oparoson

from unicodedata import normalize
def nfc_equal(str1,str2):
    return normalize('NFC',str1) == normalize('NFC',str2)

def fold_equal(str1,str2):
    return (normalize('NFC',str1).casefold() ==
            normalize('NFC',str2).casefold())

if __name__ == '__main__':
    s1 = 'café'  # 作为组合字符
    s2 = 'cafe\u0301'  # 分解为基字符和重音符
    print(s1 == s2)
    print(nfc_equal(s1, s2))
    fold_equal('A','a')
# 4.6.3 极端规划化 去掉变音符号
# sanitize.py
import unicodedata
import string

def shave_marks(txt):
    '''去掉全部变音符号'''
    norm_txt = unicodedata.normalize('NFD',txt)
    shaved = ''.join(c for c in norm_txt
                     if not unicodedata.combining(c))
    return unicodedata.normalize('NFC',shaved)

if __name__ == '__main__':
    t = '4² \u00BD café'
    print(shave_marks(t))
# 4.9.1正则表达式中的字符串和字节序列
# ramanujan.py
import re
re_numbers_str = re.compile(r'\d+')
re_words_str = re.compile(r'\w+')
re_numbers_bytes = re.compile(rb'\d+')
re_words_bytes = re.compile(rb'\w+')

text_str = ("Ramanujan saw \u0be7\u0bed\u0be8\u0bef"
            "as 1729 = 1³+12³ = 9³ + 10³.") # 在字符串编译时与前一个拼接起来
text_bytes = text_str.encode('utf8') # 字节序列只能用于字节序列的正则表达式搜索
print('Text',repr(text_str),sep='\n')
print('Numbers')
print(' str :',re_numbers_str.findall(text_str)) # 能匹配泰米尔数字和ASCII数字
print(' bytes :',re_numbers_bytes.findall(text_bytes)) # 只能匹配ASCii数字
print('words')
print(' str :',re_words_str.findall(text_str))  # 能匹配字母\上标\泰米尔数字\和sacii数字
print(' Bytes :',re_words_bytes.findall(text_bytes)) # 只能匹配ascii的字母和数字

"""
Text
'Ramanujan saw ௧௭௨௯as 1729 = 1³+12³ = 9³ + 10³.'
Numbers
 str : ['௧௭௨௯', '1729', '1', '12', '9', '10']
 bytes : [b'1729', b'1', b'12', b'9', b'10']
words
 str : ['Ramanujan', 'saw', '௧௭௨௯as', '1729', '1³', '12³', '9³', '10³']
 Bytes : [b'Ramanujan', b'saw', b'as', b'1729', b'1', b'12', b'9', b'10']"""

35岁学Python,也不知道为了啥?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值