流畅的python读书笔记④:文本和字节序列
人类使用文本,计算机使用字节序列。
——Esther Nam 和 Travis Fischer
“Character Encoding and Unicode in Python”
文章目录
- 4.1 字符问题
- 4.2 字节概要
- 4.3 基本的编解码器
- 4.4 处理编解码错误
- 4.4.1 处理UnicodeEncodeError(编码错误)
- 4.4.2 处理 UnicodeDecodeError(解码错误)
- 4.4.5 BOM 有用的鬼符
- 4.5 处理文本文件
- 4.6 规范化Unicode字符串
- 4.6.1 大小写折叠
- 4.6.3 极端的规范化:从Unicode到ASCII
- 4.7 对Unicode排序
- 4.8 Unicode数据库
- 声明
4.1 字符问题
字符是什么?
在 2015 年,“字符”的最佳定义是 Unicode 字符。因此,从 Python 3 的 str 对象中获取的元素是 Unicode 字符。这相对于python 2的 str 对象中获取的原始字节序列来说无异于更加方便——你看见几个字符就能获取到几个对象。
Unicode 标准把字符的标识和具体的字节表述进行了如下的明确区分。
- 字符的标识,即码位,是 0-1 114 111 的数字(十进制),在 Unicode 标准中以 4-6个十六进制数字表示,而且加前缀“U+”。
- 字符的具体表述取决于所用的编码。编码是在码位和字节序列之间转换时使用的算法。
把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码。
>>> s = '流畅的python'
>>> len(s)
9
>>> b = s.encode('utf8')
>>> b
b'\xe6\xb5\x81\xe7\x95\x85\xe7\x9a\x84python'
>>> len(b)
15
>>> b.decode('utf8')
'流畅的python'
4.2 字节概要
Python 内置了两种基本的二进制序列类型:不可变 bytes 类型和可变 bytearray 类型。bytes 或 bytearray 对象的各个元素是介于 0~255(含)之间的整数,而非单个字符(这与ASCII码十分相似)。然而,二进制序列的切片始终是同一类型的二进制序列,包括长度为 1 的切片。这说明在处理其时务必要将其当成整体来看待。
>>> s = bytes('流畅的python', encoding='utf8')
>>> s
b'\xe6\xb5\x81\xe7\x95\x85\xe7\x9a\x84python'
>>> s[0]
230
>>> s[:1]
b'\xe6'
>>> s_arr = bytearray(s)
>>> s_arr
bytearray(b'\xe6\xb5\x81\xe7\x95\x85\xe7\x9a\x84python')
>>> s_arr[-1:]
bytearray(b'n')
除了格式化方法(format 和 format_map)和几个处理 Unicode 数据的方法(包括casefold、isdecimal、isidentifier、isnumeric、isprintable 和 encode)之外,str 类型的其他方法都支持 bytes 和 bytearray 类型。这意味着,我们可以使用熟悉的字符串方法处理二进制序列,如 endswith、replace、strip、translate、upper等,只有少数几个其他方法的参数是 bytes 对象,而不是 str 对象。
二进制序列有一个特有的方法,fromhex,它的作用是解析十六进制数字对(数字对之间的空格是可选的),构建二进制序列:
>>> bytes.fromhex('31 4B CE A9') b'1K\xce\xa9'
二进制序列对象还有其本身的构造方法,传入:
一个 str 对象和一个 encoding 关键字参数。
一个可迭代对象,提供 0~255 之间的数值。
一个实现了缓冲协议的对象(如bytes、bytearray、memoryview、array.array);此时,把源对象中的字节序列复制到新建的二进制序列中。
4.3 基本的编解码器
Python 自带了超过 100 种编解码器(codec, encoder/decoder),用于在文本和字节之间相互转换。每个编解码器都有一个名称,如 ‘utf_8’,而且经常有几个别名,如’utf8’、‘utf-8’ 和 ‘U8’。这些名称可以传给open()、str.encode()、bytes.decode() 等函数的 encoding 参数。
>>> for codec in ['latin_1', 'utf_8', 'utf_16']:
... print(codec, 'El Niño'.encode(codec), sep='\t')
...
latin_1 b'El Ni\xf1o'
utf_8 b'El Ni\xc3\xb1o'
utf_16 b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'
4.4 处理编解码错误
4.4.1 处理UnicodeEncodeError(编码错误)
我们通过传入errors参数来改变处理错误的方法
>>> msg = '北京2022'
>>> msg.encode('utf8') # utf?格式可以处理任意字符
b'\xe5\x8c\x97\xe4\xba\xac2022'
>>> msg.encode('utf16')
b'\xff\xfe\x17S\xacN2\x000\x002\x002\x00'
>>> msg.encode('cp437') # 'cp437'无法编码中文,默认处理方式'strict'抛出UnicodeEncodeError
Traceback (most recent call last):File "<input>", line 1, in <module>File "D:\py\Anaconda\lib\encodings\cp437.py", line 12, in encodereturn codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-1: character maps to <undefined>
>>> msg.encode('cp437', errors='ignore') # errors='ignore'跳过无法编码的字符
b'2022'
>>> msg.encode('cp437', errors='replace') # errors='replace'将无法编码的字符替换成'?'
b'??2022'
>>> msg.encode('cp437', errors='xmlcharrefreplace') # errors='xmlcharrefreplace'将无法编码的字符替换成 XML 实体。
b'北京2022'
这种方法是可扩展的,详见:参考文档
4.4.2 处理 UnicodeDecodeError(解码错误)
不同的编码方式所得的同一编码,其解码肯定不相同。
乱码字符称为鬼符(gremlin)或 mojibake(文字化け,“变形文本”的日文)。演示了使用错误的编解码器可能出现鬼符或抛出UnicodeDecodeError。
>>> octets = b'Montr\xe9al' # 使用 latin1 编码的“Montréal”
>>> octets.decode('cp1252') # 'cp1252'能正确处理
'Montréal'
>>> octets.decode('iso8859_7') # iso8559_7用以编码希腊文,无法正确处理
'Montrιal'
>>> octets.decode('koi8_r') # koi8_r编码俄文
'MontrИal'
>>> octets.decode('utf_8') # 抛出UnicodeDecodeError错误
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5:
invalid continuation byte
>>> octets.decode('utf_8', errors='replace') # 将无法处理的编码替换成�(码位是 U+FFFD)
'Montr�al'
4.4.5 BOM 有用的鬼符
UTF-16编码在小端设备和大端设备中,编码的储存顺序不同。为了区分,UTF-16 编码在要编码的文本前面加上特殊的不可见字符 ZERO WIDTH NO-BREAK SPACE(U+FEFF)。在小字节序系统中,这个字符编码为 b’\xff\xfe’(十进制数 255, 254)。U+FEFF编码不能被打印,在小字节序编码中,字节序列b’\xff\xfe’ 必定是 ZERO WIDTH NO-BREAK SPACE。
UTF-16 有两个变种:UTF-16LE,显式指明使用小字节序;UTF-16BE,显式指明使用大字节序。如果使用这两个变种,不会生成 BOM。
UTF-8的一大优势是,不管设备使用哪种字节序,生成的字节序列始终一致,因此不需要 BOM。尽管如此,有些应用依旧会在UTF-8文件开头添加BOM。
4.5 处理文本文件
目前处理文本的最佳实践是“Unicode三明治”。
在不同的系统中使用的默认编码(博主的电脑是cp936)不一样,而python如果读取文件时未指定默认编码。这就是说输出可能不如你意。
open('cafe.txt', 'w', encoding='utf_8').write('café')
file = open('cafe.txt')
print(file)
# <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp936'>
print(file.read())
# caf茅
file.close()
file = open('cafe.txt', encoding='utf8')
print(file.read())
# café
如果你的代码只运行在你自己的电脑中,那肯定不需要注意编码问题,然而需要在多台设备中或多种场合下运行的代码,一定不能依赖默认编码。打开文件时始终应该明确传入 encoding= 参数,因为不同的设备使用的默认编码可能不同,有时隔一天也会发生变化。
4.6 规范化Unicode字符串
由于Unicode中组合字符的存在,相同的Unicode字面量可能有不同的编码,如
>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False
可以看到,s1, s2打印的结果是一样的,但是表达式s1 == s2
却是不成立的。在 Unicode 标准中,'é’和 ‘e\u0301’ 这样的序列叫“标准等价物”(canonical equivalent),应用程序应该把它们视作相同的字符。但是,Python 看到的是不同的码位序列,因此判定二者不相等。
此时应该使用unicodedata.normalize函数提供的Unicode规范化。其有四种模式。
- NFC 使用最少的码位构成等价的字符串
- NFD 把组合字符分解成基字符和单独的组合字符
- NFKC 和 NFKD 形式中,各个兼容字符会被替换成一个或多个“兼容分解”字符,即便这样有些格式损失,但仍是“首选”表述。二分之一’½’(U+00BD)经过兼容分解后得到的是
三个字符序列 ‘1/2’。
需要注意的是除非特殊情况,否则不用后两种规范化进行储存,原因是其可能会出现数据丢失,而仅将其用于搜索和索引。
4.6.1 大小写折叠
py3新增的str.casefold()方法将会将所有的文本转换为小写。与str.lower()不同,str.casefold()是py对Unicode的专门的解决方案,其进行转换的结果将于前者有些许不同。但是这只占Unicode字符的0.11%(116/110 122)。
4.6.3 极端的规范化:从Unicode到ASCII
利用上面提到的一些函数和方法,我们可以自定义实现一些函数,使得字符串被极端的规范化——甚至使其与最简单的字符集之一,ASCII码对应。
4.7 对Unicode排序
原生python下虽然能对非ASCII码进行排序,但是其设置比较复杂,且容易更改全局的字符编码方案,条件比较苛刻。
对Unicode排序存在一个第三方库:PyUCA。其能比较方便的实现排序。
>>> import pyuca
>>> coll = pyuca.Collator()
>>> fruits = ['caju', 'atemoia', 'cajá', 'açaí', 'acerola']
>>> sorted_fruits = sorted(fruits, key=coll.sort_key)
>>> sorted_fruits
['açaí', 'acerola', 'atemoia', 'cajá', 'caju']
4.8 Unicode数据库
import unicodedata
import rere_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')
参考链接
声明
本文来自《流畅的python》以及笔者自己的思考,如有错误,欢迎指正。
流畅的python读书笔记④:文本和字节序列相关推荐
- 流畅的Python读书笔记
流畅的Python 说明 我发现流畅的python更适合我现在看,因为它写的很详细.而effective python知识点不是很连贯,我先看完这本书,再去过一遍effective python吧! ...
- 流畅的python读书笔记-第一章Python 数据模型
第一章 python数据类型 1 隐式方法 利用collections.namedtuple 快速生成类 import collectionsCard = collections.namedtuple ...
- python读书笔记2000_流畅的Python读书笔记
特殊方法的存在是为了Python解释器调用的,你自己并不需要去调用他们,比如说my_object.len()这种写法是没有的,应该使用len(my_object).在使用len(my_object)的 ...
- python vector_[流畅的Python]读书笔记之十三运算符重载
运算符重载 Python 关于运算符重载的规则: 不能重载内置类型的运算符 不能新建,只能重载 某些运算符不能重载--is.and.or 和 not 一元运算符 __neg__ __pos__ __i ...
- python用可变参数求积_流畅的python读书笔记-第八章-对象引用、可变性和垃圾回收...
对象不是个盒子 class Gizmo: def __init__(self): print('Gizmo id: %d' % id(self)) x = Gizmo() print(x) y = G ...
- 流畅的Python读书笔记-第八章-对象引用、可变性和垃圾回收
第8章:对象引用,可变性和垃圾回收 在Python里面变量不是盒子,而是便利贴,类似于Java中的引用变量,因此最好把它们理解为附加在对象上的标注. 因为变量不过是标注,因此无法阻止为对象贴上多个标注 ...
- 读书笔记:《流畅的Python》第4章 文本和字节序列
# 第四章 文本和字节序列"""内容提要:1.Unicode字符串2.二进制序列3.在二者之间转换使用的编码4.字符/码位/字节表述5.bytes/bytearray/m ...
- 流畅的python学习笔记(三):数据结构(3:文本和字节序列)
文本和字节序列 大纲 1. 字符问题 2. 字节概要 2.1 结构体和内存视图 3. 基本的编解码器 4. 了解编解码问题 4.1 处理UnicodeEncodeError 4.2 处理Unicode ...
- [流畅的Python][4][文本和字节序列]
第4章 文本和字节序列 人类使用文本,计算机使用字节序列.--------Esther Nam和Travis Fischer Python3明确地区分了人类可读的文本字符串和原始的字节序列 4.1 字 ...
最新文章
- 嵌入式系统之温限使用
- 支付宝人脸数据被共享?李开复道歉
- 中文微博与社会化媒体的区域距离
- vscode可以打开jupyternotebook吗_刚刚,官方宣布 VS Code 支持 Python 全开发了!
- leetcode 1006. 笨阶乘
- 打造高效前端工作环境-tmuxinator
- 实现一个简易版的微博,包含 client 和 server 两部分,并实现四个基础功能:关注、取关、发微博、获取用户微博列表
- 实现添加商品信息功能
- Python bcrypt 加密验证密码
- 标签系统 -- 用户画像
- 如何为Java面试准备项目经验
- 令人啼笑皆非的不靠谱产品是如何诞生的?
- python群发邮件 不进垃圾箱_邮件群发不进垃圾箱
- 【五校联考2015 8.20】宝藏
- 挂代理后git依然无法XXX
- 马王堆汉墓帛书‧老子——乙本释文(道经)
- 使用git拉取远程仓库代码
- 【GNURadio实验报告】实验2-使用GNURadio仿真OOK信号
- Pragma section
- Cocos2dx 之 cocosbuilder的使用
热门文章
- 进程学习:进程间通信(传统通信方式)1.无名管道
- Error - 使用statsmodels报错ModuleNotFoundError: No module named 'pandas.tseries.tools'
- web端腾讯PAG初体验
- 一次项目开发中,收获的经验和教训
- 如何在前端实现甘特图?SpreadJS两招搞定
- 实验1 OpenGL初识
- Linux系统中如何查找大文件或目录文件夹的方法
- CAD打开多个文档只启动一个程序
- 苹果6s登录id显示无法连接服务器,iPhone6s appleID无法登入
- 比较好用的两款免费音频(视频)格式转换、处理软件