一、字符串的表示和存储

字符串是字符的序列,每个字符都有有一个数字作为标识,同时会有一个将标识转换为存储字节的编码方案;

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的文本和字节序列的更多相关文章

  1. 《流畅的Python》第二部分 数据结构 【序列构成的数组】【字典和集合】【文本和字节序列】

    第二部分 数据结构 第2章 序列构成的数组 内置序列类型 序列类型 序列 特点 容器序列 list.tuple.collections.deque - 能存放不同类型的数据:- 存放的是任意类型的对象 ...

  2. Fluent_Python_Part2数据结构,04-text-byte,文本和字节序列

    文本和字节序列 人使用文本,计算机使用字节序列 1. 大纲: 字符.码位和字节表述 bytes.bytearray和memoryview等二进制序列的独特特性 全部Unicode和陈旧字符集的编解码器 ...

  3. Python 文本和字节序列

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } Python 3 明确区分了人类可读的文本字符串和原始的字节序列.隐式 ...

  4. python高级(四)—— 文本和字节序列(编码问题)

    本文主要内容 字符 字节 结构体和内存视图 字符和字节之间的转换——编解码器 BOM鬼符  标准化Unicode字符串 Unicode文本排序 python高级——目录 文中代码均放在github上: ...

  5. 流畅的python第四章文本和字节序列学习记录

    字符问题 把码位转化成字节序列的过程是编码,把字节序列转化成码位的过程是解码 把unicode字符串当成人类可读的文本,码位当成机器可读的, 将字节序列编程人类可读是解码,把字符串编码成字节序列是编码 ...

  6. Python文本和字节序列

    ASCII码 早期人们用8位二进制来编码英文字母(最前面的一位是0) 也就是说,将英文字母和一些常用的字符和这128种二进制0.1串一一对应起来, 比如:大写字母“A”所对应的二进制位“0100000 ...

  7. python教程(四)·序列

    距离上次的小项目已经休息了很长一段时间,是时候来继续本系列教程了.这一节开始我们将深入python中的数据结构. 序列的概念 在python中,最基本的数据结构是序列,序列包含一个或多个元素,每个元素 ...

  8. Python菜鸟文本处理4种方法

    自从认识了python这门语言,所有的事情好像变得容易了,作为小白,逗汁儿今天就为大家总结一下python的文本处理的一些小方法. 话不多说,代码撸起来. python大小写字符互换 在进行大小写互换 ...

  9. ubuntu下把python脚本转为二进制字节码文件

    ubuntu下把python脚本转为二进制字节码文件 听语音 原创 | 浏览:354 | 更新:2017-12-22 14:48 1 2 3 4 5 6 7 分步阅读 自己拥有个几个python脚本文 ...

随机推荐

  1. 1102 Invert a Binary Tree——PAT甲级真题

    1102 Invert a Binary Tree The following is from Max Howell @twitter: Google: 90% of our engineers us ...

  2. Django Admin 配置和定制基本功能(基本二次开发配置)

    一 列表显示页面  1. list_display,列表时,定制显示的列 @admin.register(models.UserInfo) class UserAdmin(admin.ModelAdm ...

  3. Python爬虫系统化学习(2)

    Python爬虫系统学习(2) 动态网页爬取 当网页使用Javascript时候,很多内容不会出现在HTML源代码中,所以爬取静态页面的技术可能无法使用.因此我们需要用动态网页抓取的两种技术:通过浏览 ...

  4. 用Python来控制Autocad的打印------以Pycomcad为例

    from pycomcad import * #以pycomcad作为接口库为例 import win32com acad=Autocad() 打印最重要的设置都在上面的界面中,下面对这些个界面,用P ...

  5. 图文详解:阿里宠儿【小兔】RabbitMQ的养成攻略

  6. DES加密详解

    目录 1 根据输入的秘钥得到16个子秘钥 1.1 大致流程 1.2 利用PC-1从K_0中挑出K_1 1.3 利用PC-2从K_1中挑出16个子秘钥 2 利用16个子秘钥对明文进行加密 2.1 大致流 ...

  7. windows本地连接虚拟机上的ubuntu的redis,以及无法连接解决方法(redisDesktopManager Jedis详细步骤)

    一.环境 1.ubuntu20.04 . redis 5.0.7 在ubuntu上下载redis,执行命令 sudo apt install redis 2.redisDesktopManager下载 ...

  8. C++数组的存储与初始化

    下面随笔给出C++数组的存储与初始化的细节内容. 数组的存储与初始化 一维数组的存储 数组元素在内存中顺次存放,它们的地址是连续的.元素间物理地址上的相邻,对应着逻辑次序上的相邻. 例如:

  9. BERT 服务化 bert-as-service

    bert-as-service 用 BERT 作为句子编码器, 并通过 ZeroMQ 服务托管, 只需两行代码就可以将句子映射成固定长度的向量表示; 准备 windows10 + python3.5 ...

  10. 2020年12月-第02阶段-前端基础-CSS Day05

    CSS Day05 1. 学成在线页面制作 理解 能够说写单页面我们基本的流程 能说出常见的css初始化语句 能说出我们CSS属性书写顺序 应用 能利用ps切图 能引入外部样式表 能把psd文件转换为 ...