用Python理清编码问题:BOM、换行符和编码原则

共 4608字,需浏览 10分钟

 ·

2021-04-14 21:55

BOM

自标记文件编码,UTF编码系列采用的是BOM技术,就是使用一个特殊字符(zero width no-break space),一个Unicode没用到的码位,其值为0XFEFF,放置在文件开头,当编辑器读的时候,看到这个BOM就知道文件采用的什么编码。
es = 'A'
codes = ['utf-32','utf-16']
print([es.encode(code) for code in codes])
其输出[b'\xff\xfe\x00\x00A\x00\x00\x00', b'\xff\xfeA\x00'],字节序列开头的\xff\xfe就是BOM,有时候他可能是\xfe\xff的形式,这说明文本是大字节序。0xFEFF的UTF-8编码是0x\ef0x\bb0x\bf。
当我们使用UTF-16LE或者BE编码时,其输出不带BOM头。这是因为程序认为我们既然明确指定了带大小端的编码,说明我们知道并掌握自己怎么使用字节序列。

Windows记事本中的BOM

BOM技术在Windows的记事本中比较常见,新建一个文本文件,写入相同内容,然后另存为记事本支持的四种格式:ANSI、Unicode、Unicode big endian、utf-8,查看其二进制形式,可见其BOM字段和相应编码的字节序列。Python打开文件时的'b'选项,使用二进制模式读取。
# 文件内容都是:abAB巩★☆,但另存为时选择的编码格式不同。

fs = ['ansi.txt','unicode.txt','unicode big endian.txt','utf-8.txt']

for f in fs:
    with open(f,'rb') as f_:
        print(f_.readline())
输出是:
b'abAB\xb9\xae\xa1\xef\xa1\xee'
b'\xff\xfea\x00b\x00A\x00B\x00\xe9\x5d\x05\x26\x06\x26'
b'\xfe\xff\x00a\x00b\x00A\x00B\x5d\xe9\x26\x05\x26\x06'
b'\xef\xbb\xbfabAB\xe5\xb7\xa9\xe2\x98\x85\xe2\x98\x86'
从字符巩可以看出
  • ANSI实际采用的是GB系列编码。
  • Unicode.txt和unicode big endian.txt存储编码是UTF-16。更具体的,根据BOM信息,可知unicode.txt实际格式是UTF-16LE,而unicode big endian.txt实际格式是UTF-16BE。
  • UTF-8是带有BOM头的UTF-8存储格式。

其它编辑器的BOM

记事本使用BOM作文本编码信息的自标记,但是这并不是对编辑器的强制要求,也就是可带可不带,比如Linux下VIM编辑器,就不带BOM信息,这也是跨操作系统、编辑器编辑时,经常遇到问题的地方。

vim设置中,查询BOM:set bomb?,取消BOM::set nobomb,设置BOM:set bomb

上文中的ANSI为啥是GB系列编码呢?

Windows的ANSI和代码页

至于ANSI格式为什么用GB系编码形式,需要多说一句。ANSI是美国国家标准协会,各国(非拉丁语系国家)指定自身文字编码,得到ANSI的认可之后使用。所以各个国家的ANSI实际指代编码各不相同,比如中国是GB2312,日本JIT,台湾地区Big5。ANSI实际是指向具体编码的“指针”。
相较于,Windows记事本用ANSI来指代本地区的编码;Windows系统用代码页指代本系统编码,但这两个概念可视为一个概念。代码页或ANSI从命令行窗口输入chcp可见。比如简体中文的操作系统会返回活动代码页;936,这就是CP936,也就是GBK编码规范。代码页也是指向具体编码的“指针”。
在记事本选择ANSI格式,实际是选择当前系统的活动代码页指代的具体编码,本例中选记事本ANSI,就是选代码页936,代码页CP936就指向GBK编码。
在Unix系统和Windows系统间切换时,经常出现文本出现^M和所有行挤成一行的问题。

换行符问题

Window下,UE编辑器或者vim的十六进制打开文件时,可看到文件的最后有0x0d0a这两个字节。这分别是Windows中的换行(0x0a或\n)和回车(0x0d或\r)的ASCII码。

VIM编辑器使用命令:% ! xxd转换位十六进制显示;使用命令:% ! xxd -r转换为文本显示。

这要从机械打字机说起,机械打字机打印完一整行之后,需要手动将“车”拉回到左侧,在回车的过程中,打字机自动完成了换行的操作,就可以继续打印第二行。
  • Windows系统借鉴这两个字符,使用回车和换行表示一行完成,另起一行。
  • Unix系统仅仅使用了换行符。
    • Unix系统打开Windows系统的文件时,由于多了回车符号,常看到行末尾出现奇怪的^M符号,这是Unix解析\r产生的。
    • Windows系统里打开Unix系统文件时,由于少了回车符,所有行挤在一起,变成一行。
  • Mac系统使用回车\r表示换行。

Python编程中编码问题

以上,我们理清了字符编码的发展脉络,知道了BOM以及不同操作系统、编辑器对BOM的应用。在Python编程中,我们如何处理编码问题呢?有以下几个注意事项:

建议使用UTF-8

为了国际交流的便捷,Python使用UTF-8作为默认编码格式,在日常网页编程,数据库默认编码,以及文本存储时都建议使用UTF-8。

decode和encode用法

什么时候encode,什么时候decode呢?需要记住一个原则:Python3字符串是Unicode码位,也就是说所有Python字面量实际是Unicode码位整数。所有转换都要以Unicode码位为中间体:
  • Unicode码位转字节序列时用encode
  • 字节序列转为Unicode时用decode
str = 'abAB巩'    #Unicode码位
gbk_seq = str.encode('gbk'#Unicode码位转GBK字节序列
str2 = gbk_seq.decode('gbk'#GBK字节序列转Unicode码位
utf_seq= str2.encode('utf-8'#Unicode码位转UTF-8字节序列
print(str,str2,gbk_seq,utf_seq)

Python2字面量实际是带有编码格式的字节序列。

三明治原则

三明治原则是在读写文件时,一定显式指定文件编码,确保Python程序中只处理Unicode码位,而不涉及字节序列。只有在输入和输出时,文件才转换为相应编码下的字节流。这种两端有具体编码,内里只有Unicode的处理方式,像两片面包夹着食材,所以称为三明治原则。
with open('ansi.txt','r',encoding='gbk')as f,open('u8.txt','w',encoding='utf-8') as f2:
    s = f.readline()
    s = s[::-1]
    f2.write(s)
以上代码,s字符串相关的处理都是Unicode码位。只有输入和输出时,才指定了相应的编码GBK和UTF-8。

不要使用python2

不要使用python2!Python2中的Str类型,既是字符串,又是字节序列类型,特别复杂。非有特殊要求,建议不要使用。以下内容,足以一窥Python2的复杂:
Python2中,s = 'abAB巩’,是一个字节序列,它的具体值根据采用的编码(GBK,UTF-8)而不同。u = u'abAB巩'表示的是Unicode码位。另外Python2中的编码类型声明# -*- coding:utf-8声明的是程序中常量的编码类型,需要和文本保存编码类型一致。

使用chardet推测字节序列的编码

若无法确定字节流的编码,模块chardet可用来推测字节流的编码。

import chardet
chardet.detect(b'\xef\xbb\xbfabAB\xe5\xb7\xa9\xe2\x98\x85\xe2\x98\x86')
返回是一个字典{'encoding':'utf-8',...},指出了最可能的编码及其可信度。

总结

  • 为了自标记文件编码,引入了BOM标签。我们分析了BOM在记事本中的表现,了解了BOM并不是强制要求。

  • 了解了BOM、换行符导致的Linux和Windows之间的许多问题。

  • 学习了Python中编程的三明治原则;读写文件时,显式指明文件编码。

本系列总结

  • 从ASCII的简单,但不支持其他国家文字。

  • 到扩展ASCII支持其他国文字,但产生乱码。

  • 再到MBCS多字节编码可以支持汉语这种很多字符的语言,但由于太多语言编码导致各自为战互不兼容。

  • Unicode统一了世界各语言字符。Unicode几种编码形式中;

    • UTF-32简单,但浪费严重。

    • UTF-16使用两个字节为单位存储,节省了空间。

    • UTF-8使用一个字节直接存储,是效率、空间的平衡。

  • 为了自标记文件编码,引入了BOM标签。我们分析了BOM在记事本中的表现,了解了BOM并不是强制要求。

  • 了解了BOM、换行符导致的Linux和Windows之间的许多问题。

  • 学习了Python中编程的三明治原则;读写文件时,显式指明文件编码。

人类交流因语言文字不同,造成很大困扰,无数IT从业人员也遇到各种乱码折磨,不认真攻克编码问题,他就会时不时跳出来。需要我们认清编码是发展和优化平衡的结果,包含了一系列优化、例外和兼容。

本文代码

https://gitee.com/gongqingkui/codes/0qku9tc6az3j2dvoy5s8w39

作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。gongqingkui at 126.com

赞 赏 作 者



更多阅读



2020 年最佳流行 Python 库 Top 10


2020 Python中文社区热门文章 Top 10


5分钟快速掌握 Python 定时任务框架

特别推荐




点击下方阅读原文加入社区会员

浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报