Python的文本和字节序列
一、字符串的表示和存储
字符串是字符的序列,每个字符都有有一个数字作为标识,同时会有一个将标识转换为存储字节的编码方案;
s = 'hello world python'
for c in s:
print(c, end=' ')
h e l l o w o r l d p y t h o n
ACSII为协议内的每个字符分别对应一个数字,然后以这个数字的二进制形式存储到计算机;
s = 'hello world python'
for c in s:
num = ord(c)
print(num, format(num, 'b'))
104 1101000
101 1100101
108 1101100
108 1101100
111 1101111
32 100000
119 1110111
111 1101111
114 1110010
108 1101100
100 1100100
32 100000
112 1110000
121 1111001
116 1110100
104 1101000
111 1101111
110 1101110
ACSII协议覆盖的字符十分有限,使用一个字节就可以保存,这也是其比较简单的根源;
s = b'é'
File "<ipython-input-19-b82fcf157fe5>", line 1
s = b'é'
^
SyntaxError: bytes can only contain ASCII literal characters.
unicode标准为每个字符制定一个数字作为code point;
s = 'è ç í'
for c in s:
print(ord(c))
232
32
231
32
237
unicode支持大量的字符,需要使用多个字节来存储,这就涉及到字节的大小端、空间占用及与ACSII的兼容性问题;
UTF-32编码方案直接使用4个字节来承载code poin的二进制形式,涉及大小端问题,比较浪费空间,使用较少;
s = 'èçí'
for b in s.encode('utf_32be'):
print(hex(b), end=' ')
print()
for b in s.encode('utf_32le'):
print(hex(b), end=' ')
print()
for b in s.encode('utf_32'):
print(hex(b), end=' ')
0x0 0x0 0x0 0xe8 0x0 0x0 0x0 0xe7 0x0 0x0 0x0 0xed
0xe8 0x0 0x0 0x0 0xe7 0x0 0x0 0x0 0xed 0x0 0x0 0x0
0xff 0xfe 0x0 0x0 0xe8 0x0 0x0 0x0 0xe7 0x0 0x0 0x0 0xed 0x0 0x0 0x0
UTF-16编码方案根据前两个字节的范围来确定使用两个字节还是四个字节,虽然比UTF-32节省空间,但是使用也比较少;
s = 'èçí'
for b in s.encode('utf_16be'):
print(hex(b), end=' ')
print()
for b in s.encode('utf_16le'):
print(hex(b), end=' ')
print()
for b in s.encode('utf_16'):
print(hex(b), end=' ')
0x0 0xe8 0x0 0xe7 0x0 0xed
0xe8 0x0 0xe7 0x0 0xed 0x0
0xff 0xfe 0xe8 0x0 0xe7 0x0 0xed 0x0
UTF-8也使用变长字节,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数为1~4不等;
s = 'èçí'
for b in s.encode('utf_8'):
print(hex(b), end=' ')
0xc3 0xa8 0xc3 0xa7 0xc3 0xad
utf-16和utf-32编码方案默认生成的字节序列会添加BOM(byte-order mark)即\xff\xfe,指明编码的时候使用Interl CPU小字节序。
二、字节数组
bytes和bytearray的元素都是介于0-255之间的整数,但是通过字符编码方案也可以存储任何的字符串;字节数组切片还是对应的字节数组;
字节数组可以直接显示ASCII字符;
s = 'helloèçí'
b_arr = bytes(s, 'utf_8')
print(type(b_arr))
print(type(b_arr))
for b in b_arr:
print(b, end=' ')
print()
print('element of bytes is int number', b_arr[0])
print('splice of bytes is bytes',end = ' ' )
b_arr_splice = b_arr[:1]
print(b_arr_splice)
num_b_arr = bytes([299])
<class 'bytes'>
b'hello\xc3\xa8\xc3\xa7\xc3\xad'
104 101 108 108 111 195 168 195 167 195 173
element of bytes is int number 104
splice of bytes is bytes b'h'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-61-b8f064f91cf5> in <module>()
13 print(b_arr_splice)
14
---> 15 num_b_arr = bytes([299])
ValueError: bytes must be in range(0, 256)
struct模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的字节序列。struct模块能处理bytes、bytearray和memoryview对象。
import struct
record_format = 'hd4s'
pack_bytes = struct.pack(record_format, 7 , 3.14,b'gbye')
print(type(pack_bytes))
print(pack_bytes)
with open('struct.b', 'wb') as fp:
fp.write(pack_bytes)
record_size = struct.calcsize(record_format)
with open('struct.b', 'rb') as fp:
record_bs = fp.read(record_size)
print(struct.unpack(record_format, record_bs))
三、不要依赖默认编码
读写文本文件的时候最好要显示的指定编码方案,防止编码方案不匹配出现乱码或者错误;
open('cafe.txt', 'w', encoding='utf-8').write('café')
fp = open('cafe.txt')
print(fp)
print(fp.read())
由于Linux的默认编码是UTF-8,所以运行结果正常
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='UTF-8'>
café
但是在windows 10上执行就不这么幸运了,我们可以看到IO的默认编码方案是cp936
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp936'>
caf茅
在Linux和windows上分别执行以下探测默认编码方案的代码
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()
'''
with open('encoding', 'w') as my_file:
for expression in expressions.split():
value = eval(expression)
print(expression.rjust(30), '->', repr(value))
在Ubuntu上执行,可以看到输出的都是UTF-8;
locale.getpreferredencoding() -> 'UTF-8'
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'UTF-8'
sys.stdout.isatty() -> True
sys.stdout.encoding -> 'utf-8'
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'utf-8'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'utf-8'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'utf-8'
在windows 10上执行,locale.getpreferredencoding()和my_file的编码都是cp936;
locale.getpreferredencoding() -> 'cp936'
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'cp936'
sys.stdout.isatty() -> True
sys.stdout.encoding -> 'utf-8'
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'utf-8'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'utf-8'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'utf-8'
如果没有指定编码方案,操作文本文件的时候默认使用locale.getpreferredencoding(),在windows10上将python的执行结果重定向到文件,可以看到sys.stdout.encoding变成了cp936;
locale.getpreferredencoding() -> 'cp936'
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'cp936'
sys.stdout.isatty() -> False
sys.stdout.encoding -> 'cp936'
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'utf-8'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'utf-8'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'utf-8'
python使用sys.getdefaultencoding()进行二进制数据与字符串之间的转换;
sys.getfilesystemencoding( )用于编解码文件名(不是文件内容)。把字符串参数作为文件名传给open( )函数时就会使用它;
四、规范化字符串之后进行比较
因为Unicode有组合字符(变音符号和附加到前一个字符上的记号,打印时作为一个整体),所以字符串比较起来很复杂。
# 同样的一个字符会有不同的构成方式
s1 = 'café'
s2 = 'cafe\u0301'
print((s1, s2))
print((len(s1), len(s2)))
print(s1 == s2)
('café', 'café')
(4, 5)
False
U+0301是COMBINING ACUTE ACCENT,加在“e”后面得到“é”。在Unicode标准中,'é'和'e\u0301'这样的序列叫“标准等价物”(canonical equivalent),应用程序应该把它们视作相同的字符。但是,Python看到的是不同的码位序列,因此判定二者不相等。
Python中unicodedata.normalize函数提供的Unicode规范化。这个函数的第一个参数是这4个字符串中的一个:'NFC'、'NFD'、'NFKC'和'NFKD'。NFC(Normalization Form C)使用最少的码位构成等价的字符串,而NFD把组合字符分解成基字符和单独的组合字符。这两种规范化方式都能让比较行为符合预期:
# normalize字符串再进行比较
from unicodedata import normalize
s1 = 'café'
s2 = 'cafe\u0301'
print((s1, s2))
print((len(s1), len(s2)))
print(s1 == s2)
s1_nfc_nor = normalize('NFC', s1)
s2_nfc_nor = normalize('NFC', s2)
print((s1_nfc_nor, s2_nfc_nor))
print((len(s1_nfc_nor), len(s2_nfc_nor)))
print(s1_nfc_nor == s2_nfc_nor)
s1_nfd_nor = normalize('NFD', s1)
s2_nfd_nor = normalize('NFD', s2)
print((s1_nfd_nor, s2_nfd_nor))
print((len(s1_nfd_nor), len(s2_nfd_nor)))
print(s1_nfd_nor == s2_nfd_nor)
# ('café', 'café')
# (4, 5)
# False
# ('café', 'café')
# (4, 4)
# True
# ('café', 'café')
# (5, 5)
# True
在另外两个规范化形式(NFKC和NFKD)的首字母缩略词中,字母K表示“compatibility”(兼容性)。这两种是较严格的规范化形式,对“兼容字符”有影响。虽然Unicode的目标是为各个字符提供“规范的”码位,但是为了兼容现有的标准,有些字符会出现多次。例如,虽然希腊字母表中有“μ”这个字母(码位是U+03BC,GREEK SMALL LETTER MU),但是Unicode还是加入了微符号'µ'(U+00B5),以便与latin1相互转换。因此,微符号是一个“兼容字符”。
# NFKC的规范化
from unicodedata import normalize, name
half = '½'
print(len(half))
print(hex(ord(half)))
half_nor = normalize('NFKC', half)
print(half_nor)
print(type(half_nor))
print(len(half_nor))
for c in half_nor:
print(hex(ord(c)), end=' ')
print()
four_squared = '4²'
four_squared_no = normalize('NFKC', four_squared)
print(four_squared_no)
micro = 'µ'
micro_nor = normalize('NFKC', micro)
print(micro_nor)
print(ord(micro), ord(micro_nor))
print(name(micro), name(micro_nor))
# 1
# 0xbd
# 1⁄2
# <class 'str'>
# 3
# 0x31 0x2044 0x32
# 42
# μ
# 181 956
# MICRO SIGN GREEK SMALL LETTER MU
使用'1/2'替代'½'可以接受,微符号也确实是小写的希腊字母'µ',但是把'4²'转换成'42'就改变原意了。某些应用程序可以把'4²'保存为'42',但是normalize函数对格式一无所知。因此,NFKC或NFKD可能会损失或曲解信息。
大小写折叠其实就是把所有文本变成小写,再做些其他转换。这个功能由str.casefold( )方法(Python 3.3新增)支持。对于只包含latin1字符的字符串s,s.casefold( )得到的结果与s.lower( )一样,唯有两个例外:微符号'µ'会变成小写的希腊字母“μ”(在多数字体中二者看起来一样);德语Eszett(“sharp s”,ß)会变成“ss”。
# 大小写折叠
micro = 'µ'
print(name(micro))
micro_cf = micro.casefold()
print(name(micro_cf))
print((micro, micro_cf))
eszett = 'ß'
print(name(eszett))
eszett_cf = eszett.casefold()
print((eszett, eszett_cf))
# MICRO SIGN
# GREEK SMALL LETTER MU
# ('µ', 'μ')
# LATIN SMALL LETTER SHARP S
# ('ß', 'ss')
Google搜索涉及很多技术,其中一个显然是忽略变音符号(如重音符、下加符等),至少在某些情况下会这么做。去掉变音符号不是正确的规范化方式,因为这往往会改变词的意思,而且可能误判搜索结果。但是对现实生活却有所帮助:人们有时很懒,或者不知道怎么正确使用变音符号,而且拼写规则会随时间变化,因此实际语言中的重音经常变来变去。
# 极端规范化,去掉变音符号
import unicodedata
import string
def shave_marks(txt):
txt_nor = normalize('NFD', txt)
txt_shaved = ''.join(c for c in txt_nor if not unicodedata.combining(c))
return normalize('NFC', txt_shaved)
order = 'è ç í'
print(shave_marks(order))
greek = 'έ é'
print(shave_marks(greek))
def shave_marks_latin(txt):
txt_nor = normalize('NFD', txt)
latin_base = False
keep = []
for c in txt_nor:
if unicodedata.combining(c) and latin_base:
continue;
keep.append(c)
if not unicodedata.combining(c):
latin_base = c in string.ascii_letters
shaved = ''.join(keep)
return normalize('NFC', shaved)
print(shave_marks_latin(order))
print(shave_marks_latin(greek))
# e c i
# ε e
# e c i
# έ e
Python的文本和字节序列的更多相关文章
- 《流畅的Python》第二部分 数据结构 【序列构成的数组】【字典和集合】【文本和字节序列】
第二部分 数据结构 第2章 序列构成的数组 内置序列类型 序列类型 序列 特点 容器序列 list.tuple.collections.deque - 能存放不同类型的数据:- 存放的是任意类型的对象 ...
- Fluent_Python_Part2数据结构,04-text-byte,文本和字节序列
文本和字节序列 人使用文本,计算机使用字节序列 1. 大纲: 字符.码位和字节表述 bytes.bytearray和memoryview等二进制序列的独特特性 全部Unicode和陈旧字符集的编解码器 ...
- Python 文本和字节序列
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } Python 3 明确区分了人类可读的文本字符串和原始的字节序列.隐式 ...
- python高级(四)—— 文本和字节序列(编码问题)
本文主要内容 字符 字节 结构体和内存视图 字符和字节之间的转换——编解码器 BOM鬼符 标准化Unicode字符串 Unicode文本排序 python高级——目录 文中代码均放在github上: ...
- 流畅的python第四章文本和字节序列学习记录
字符问题 把码位转化成字节序列的过程是编码,把字节序列转化成码位的过程是解码 把unicode字符串当成人类可读的文本,码位当成机器可读的, 将字节序列编程人类可读是解码,把字符串编码成字节序列是编码 ...
- Python文本和字节序列
ASCII码 早期人们用8位二进制来编码英文字母(最前面的一位是0) 也就是说,将英文字母和一些常用的字符和这128种二进制0.1串一一对应起来, 比如:大写字母“A”所对应的二进制位“0100000 ...
- python教程(四)·序列
距离上次的小项目已经休息了很长一段时间,是时候来继续本系列教程了.这一节开始我们将深入python中的数据结构. 序列的概念 在python中,最基本的数据结构是序列,序列包含一个或多个元素,每个元素 ...
- Python菜鸟文本处理4种方法
自从认识了python这门语言,所有的事情好像变得容易了,作为小白,逗汁儿今天就为大家总结一下python的文本处理的一些小方法. 话不多说,代码撸起来. python大小写字符互换 在进行大小写互换 ...
- ubuntu下把python脚本转为二进制字节码文件
ubuntu下把python脚本转为二进制字节码文件 听语音 原创 | 浏览:354 | 更新:2017-12-22 14:48 1 2 3 4 5 6 7 分步阅读 自己拥有个几个python脚本文 ...
随机推荐
- Hbase ——Not only SQL
HBase -- NoSQL_Not Only SQL NoSQL数据库: 不遵循传统的RDBMS模型 解决数据库的可伸缩性和可用性(多机器) 数据是非关系的(可切分),不使用sql语句 不针对原子性 ...
- Django简单的使用及一些基础方法
目录 一.静态文件配置 1. 什么是静态文件 2. 静态文件的用法 3. 静态文件的动态绑定 二.请求方式与相应 1. get请求 2. post请求 3. Django后端视图函数处理请求 三.re ...
- CentOS7 下Docker最新入门教程 超级详细 (安装以及简单的使用)
转载https://blog.csdn.net/wzsy_ll/article/details/82866627 1.为什么使用Docker(本人) 最近总是频繁的在新服务器发布项目, 每次发布都需要 ...
- 剑指 Offer 56 - I. 数组中数字出现的次数 + 分组异或
剑指 Offer 56 - I. 数组中数字出现的次数 Offer_56_1 题目描述 解题思路 java代码 /** * 方法一:数位方法 */ class Offer_56_1_2 { publi ...
- 普通的一天,说一个普通的XML
什么是XML XML全称是Extensible Markup Language,译为"可扩展标记语言",常用来存储和传输信息. XML的结构 我们经常看到的XML文件是这个样子的: ...
- docker搭建redis集群和Sentinel,实现故障转移
0.引言 公司开发需要用到redis,虽然有运维自动搭建,还是记录下如何搭建redis集群和Sentinel. 采用的是vagrant虚拟机+docker的方式进行搭建. 搭建思路: 首先是借鉴下其他 ...
- ElasticSearch 进阶
目录 ElasticSearch 进阶 SearchAPI 检索信息 Query DSL 基本语法格式 查询-match 查询-match_phrase 查询-multi_match 查询-bool复 ...
- 🚩数分工作了三年,我干了件很酷的事情
从17年毕业来,一直都在干数据分析的工作.和很多转行的小伙伴一样,没有对口的科班学习,摸不清数据分析具体情况,起初充满着很多迷茫. 在刚开始的1年半中,都是自己从淘宝买些课程,最多时,网盘放了4-5T ...
- MySQL二进制安装脚本
MySQL二进制包自行百度,晚上很多查找办法 #!/bin/bash #二进制安装mysql并初始化密码为123456 mysql_name=mysql-5.7.31-linux-glibc2.12- ...
- React实用技巧
取消请求 React 中当前正在发出请求的组件从页面上卸载了,理想情况下这个请求也应该取消掉,那么如何把请求的取消和页面的卸载关联在一起呢? 这里要考虑利用 useEffect 传入函数的返回值: u ...