一个字符串是一个字符序列。字符是Unicode字符
字符的标识是码位。
字符的具体表述取决于所用的编码。

1
2
3
4
5
6
7
8
9
>>> s = 'cafe'
>>> b = s.encode('utf8') #使用UTF-8把str对象编码成bytes对象 把码位转换成字节序列
>>> a = b.decode('utf8') #使用UTF-8把bytes对象解码成str对象 把字节序列转换成码位
>>> b
>>> b'cafe'
>>> a
>>> 'cafe'
>>> s
>>> 'cafe'

bytes 字面量以b开头
python3里的 str 类型相当于python2 里的 Unicode类型

bytes 或 bytearry 对象的各个元素是介于0-255(含)之间的整数
bytes 的切片仍然是 bytes 对象
my_bytes[0] == my_bytes[:1]
书本P85

re模块中的正则表达式也能处理二进制序列

1
2
3
4
5
6
>>> a =5
>>> del a
>>> a
>>> Traceback (most recent call last):
>>> File "<stdin>", line 1, in <module>
>>> NameError: name 'a' is not defined

del 释放内存

memory views
docs.python.org/3/library/stdtypes.html#memory-views
struct
docs.python.org/3/library/struct.html

UnicodeEncodeError 把字符串转换成二进制序列时的问题
UnicodeDecodeError 把二进制序列转换成字符串时的问题
SyntaxError 如果源码的编码与预期不符,加载python模块时的问题

s.encode(‘utf8’,errors = ‘ignore’) 把无法编码的字符跳过
s.encode(‘utf8’,errors = ‘replace’) 把无法编码的字符代替为 ‘?’
s.encode(‘utf8’,errors = ‘xmlcharrefreplace’) 把无法编码的字符替换成XML实体

处理文本的最佳实践是“Unicode 三明治”。
要尽早把输入(例如读取文件时)的字节序列解码成字符串。这种三明治中的“肉片”
是程序的业务逻辑,在这里只能处理字符串对象。在其他处理过程中,一定不能编码或解码。
对输出来说,则要尽量晚地把字符串编码成字节序列。

python3内置的 open 函数会在读取文件时做必要的解码,以文本模式写入文件时还会做
必要的编码,所以调用 my_file.read()方法得到的以及传给 my_file.write(text)
方法的都是字符串对象
如果依赖默认编码,会遇到麻烦。
系统默认的编码(Windows 1252)

需要在多台设备中或多种场合下运行的代码,一定不能依赖默认编码。打开文
件时始终应该明确传入 encoding= 参数,因为不同的设备使用的默认编码可能不
同,有时隔一天也会发生变化。

如果打开文件是为了写入,但是没有指定编码参数,会使用区域设置中的默认编码,而且
使用那个编码也能正确读取文件。但是,如果脚本要生成文件,而字节的内容取决于平台
或同一平台中的区域设置,那么就可能导致兼容问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> fp = open('cafe.txt', 'w', encoding='utf_8')
>>> fp # 默认情况下,open 函数采用文本模式,返回一个 TextIOWrapper 对象。
>>> <_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
>>> fp.write('café')
>>> 4 # 在 TextIOWrapper 对象上调用 write 方法返回写入的 Unicode 字符数。
>>> fp.close()
>>> import os
>>> os.stat('cafe.txt').st_size
>>> 5 # os.stat 报告文件中有 5 个字节;UTF-8 编码的 'é' 占两个字节,0xc3 和 0xa9。
>>> fp2 = open('cafe.txt')
>>> fp2 # 打开文本文件时没有显式指定编码,返回一个 TextIOWrapper 对象,编码是区域设置
>>> #中的默认值
>>> <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'>
>>> fp2.encoding #TextIOWrapper 对象有个 encoding 属性;查看它,发现这里的编码是 cp1252。
>>> 'cp1252'
>>> fp2.read()
>>> 'café' # 在 Windows cp1252 编码中,0xc3 字节是“Ô(带波形符的 A),0xa9 字节是版权符
>>> 号。
>>> fp3 = open('cafe.txt', encoding='utf_8') # 使用正确的编码打开那个文件。
>>> fp3
>>> <_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'>
>>> fp3.read()
>>> 'café' #结果符合预期:得到的是四个 Unicode 字符 'café'。
>>> fp4 = open('cafe.txt', 'rb') # 'rb' 标志指明在二进制模式中读取文件。
>>> fp4
>>> <_io.BufferedReader name='cafe.txt'> # 返回的是 BufferedReader 对象,而不是 TextIOWrapper 对象。
>>> fp4.read() #读取返回的字节序列,结果与预期相符。
>>> b'caf\xc3\xa9'
1
2
3
4
5
6
7
8
9
10
11
locale.getpreferredencoding() -> 'cp936'# locale.getpreferredencoding() 是最重要的设置。
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'cp936'# 文本文件默认使用 locale.getpreferredencoding()。
sys.stdout.isatty() -> True# 输出到控制台中,因此 sys.stdout.isatty() 返回 True。
sys.stdout.encoding -> 'utf-8'# 因此,sys.stdout.encoding 与控制台的编码相同。
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'

关于编码默认值的最佳建议是:别依赖默认值。

使用 unicodedata.normalize 函数提供的 Unicode 规范化。这个
函数的第一个参数是这 4 个字符串中的一个:’NFC’、’NFD’、’NFKC’ 和 ‘NFKD’。
NFC(Normalization Form C)使用最少的码位构成等价的字符串
NFD 把组合字符分解成基字符和单独的组合字符。

西方键盘通常能输出组合字符,因此用户输入的文本默认是 NFC 形式。不过,安全起
见,保存文本之前,最好使用 normalize(‘NFC’, user_text) 清洗字符串。

使用 NFC 时,有些单字符会被规范成另一个单字符。这两个字符在视觉上是一样的,
但是比较时并不相等,因整理此要规范化,防止出现意外

另外两个规范化形式(NFKC 和 NFKD)的首字母缩略词中,字母 K 表
示“compatibility”(兼容性)。这两种是较严格的规范化形式,对“兼容字符”有影响。
在 NFKC 和 NFKD 形式中,各个兼容字符会被替换成一个或多个“兼容分解”字符,即便
这样有些格式损失,但仍是“首选”表述——理想情况下,格式化是外部标记的职责,不应
该由 Unicode 处理。下面举个例子。二分之一 ‘½’(U+00BD)经过兼容分解后得到的是
三个字符序列 ‘1/2’;微符号 ‘µ’(U+00B5)经过兼容分解后得到的是小写字母
‘μ’(U+03BC)。

str.casefold() 大小写折叠——把所有文本变成小写
str.casefold() 和 str.lower() 得到不同结果的有 116 个码位。

对于只包含 latin1 字符的字符串 s,s.casefold() 得到的结果与 s.lower() 一样,唯
有两个例外:微符号 ‘µ’ 会变成小写的希腊字母“μ”(在多数字体中二者看起来一样);
德语 Eszett(“sharp s”,ß)会变成“ss”。

re.compile(r’\d+’) 字符串类型
re.compile(rb’\d+’) 字节类型
字符串正则表达式有个 re.ASCII 标志,它让 \w、\W、\b、\B、\d、\D、\s
和 \S 只匹配 ASCII 字符。
re模块 https://docs.python.org/3/library/re.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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('utf_8') ➎
print('Text', repr(text_str), sep='\n ')
print('Numbers')
print(' str :', re_numbers_str.findall(text_str)) ➏
print(' bytes:', re_numbers_bytes.findall(text_bytes)) ➐
print('Words')
print(' str :', re_words_str.findall(text_str)) ➑
print(' bytes:', re_words_bytes.findall(text_bytes)) ➒
  1. 前两个正则表达式是字符串类型。
  2. 后两个正则表达式是字节序列类型。
  3. 要搜索的 Unicode 文本,包括 1729 的泰米尔数字(逻辑行直到右括号才结束)。
  4. 这个字符串在编译时与前一个拼接起来(参见 Python 语言参考手册中的“2.4.2. String
    literal concatenation”,https://docs.python.org/3/reference/lexical_analysis.html#string-literal-
    concatenation)。
  5. 字节序列只能用字节序列正则表达式搜索。
  6. 字符串模式 r’\d+’ 能匹配泰米尔数字和 ASCII 数字。
  7. 字节序列模式 rb’\d+’ 只能匹配 ASCII 字节中的数字。
  8. 字符串模式 r’\w+’ 能匹配字母、上标、泰米尔数字和 ASCII 数字。
  9. 字节序列模式 rb’\w+’ 只能匹配 ASCII 字节中的字母和数字。

评论