处理文本文件

处理文本文件

处理文本的最佳实践是“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仍然有不如人意的地方

上一篇:在vmware上搭建ubuntu20.04环境


下一篇:7-57 查找整数 (10分)