处理文本文件
处理文本的最佳实践是“Unicode三明治”。尽早把输入的的字节序列解码成字符串,然后对字符串进行处理,在其他过程中一定不能编码或解码。对输出来说,要尽量晚地把字符串编码成字节序列
在Python3中能轻松的采纳Unicode三明治的建议,因为内置的open函数会再读取文件时做必要的解码,以文本模式写入文件时还会做必要的编码,所以调用my_file.read()方法得到的以及传给my_file.write(text)方法的都是字符串对象
open('./cafe.txt', 'w', encoding='utf_8').write('café')
4
open('cafe.txt').read()
'caf茅'
写入文件时制定了UTF-8编码,但是读取文件时没那么做,因此Python假定要使用系统默认的编码(cp936),于是文件的最后一个字节解码成了字符'茅',而不是'é'。
fp = open('cafe.txt', 'w', encoding='utf_8')
fp # 默认情况下,open函数采用文本模式,返回一个TextIOWrapper对象
<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
fp.write('café') # 在TextIOWrapper对象上调用write方法返回写入的Unicode字符数
4
fp.close()
import os
os.stat('cafe.txt').st_size # os.stat报告文件中有5个字节,UTF-8编码的'é'占两个字节,0xc3和0xa9
5
fp2 = open('cafe.txt')
fp2 # 打开文本文件时没有显式指定编码,返回一个TextIOWrapper对象,编码是区域设置中的默认值
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp936'>
fp2.encoding # TextIOWrapper对象有个encoding属性,可以查看当前编码是cp936
'cp936'
fp2.read()
'caf茅'
fp3 = open('cafe.txt', encoding='utf_8') # 使用正确的编码打开文本文件
fp3
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'>
fp3.read() 结果符合预期,得到四个Unicode字符'café'
'café'
fp4 = open('cafe.txt', 'rb') # 'rb'标志指明在二进制中读取文件
fp4
<_io.BufferedReader name='cafe.txt'>
fp4.read() # 读取返回的字节序列,结果与预期相符
b'caf\xc3\xa9'
编码默认值
import sys
import 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('cafe.txt', 'w')
for expression in expressions.split():
value = eval(expresion)
print(expression.rjust(30), '->', repr(value))
locale.getpreferredencoding() -> 'cp936'
type(my_file) -> 'cp936'
my_file.encoding -> 'cp936'
sys.stdout.isatty() -> 'cp936'
sys.stdout.encoding -> 'cp936'
sys.stdin.isatty() -> 'cp936'
sys.stdin.encoding -> 'cp936'
sys.stderr.isatty() -> 'cp936'
sys.stderr.encoding -> 'cp936'
sys.getdefaultencoding() -> 'cp936'
sys.getfilesystemencoding() -> 'cp936'
在GNU/Linux和OS X中,这些编码的默认值都是UTF-8,而且多年来都是如此,因此I/O能处理所有Unicode字符。
locale.getpreferreadencoding()返回的编码是最重要的:这是打开文件的默认值,也是重定向到文件sys.stdout/stdin/stderr的默认编码。
关于编码默认值的最佳建议是:别依赖默认值
如果遵从Unicode三明治的建议,而且始终在程序中显式指定编码,那将避免很多问题。
即使把字节序列正确地转换成字符串,Unicode仍然有不如人意的地方