关于python内open函数encoding编码问题
在学python3.7的open函数时,我发现在pycharm里新建一个file_name.txt文本文件,输入中文保存,再用open(file_name,'r+')打开,再去读写时出现了一些小问题,记录一下。
场景1:
c用“w”模式新建一个不存在的文件test01.txt,并写入你好:
运行后再手动打开该文件:
发现乱码。
场景2:
我在项目里手动新建一个test02.txt,里面写入“你好”两个汉字保存,再open读取打印:
结果为:
为什么是 " 浣 犲 ソ " 这三个陌生的玩意呢?查看“你好”的16进制表示:
再查看" 浣 犲 ソ "的GBK编码16进制表示:
好像明白了:open函数用GBK编码规则解码了被UTF-8编码规则编码的test02.txt文件。前者用两个字节表示一个汉字而后者用三个。
可直接用python验证这一点(encode默认utf8):
而在场景1中恰恰相反,新建写入的时候用的是GBK,而手动打开查看的时候却用utf-8来加载,所以乱码了。
把“你好”换成“中国”再试一次:报错了!
注意:E4是位置0,AD是位置2
这是因为汉字“中国”的6字节编码中的第3、4两个字节ADE5可能没有对应的GBK编码字符,从而导致出错。
解决方法:
申明open()函数的编码方式为'utf-8',即encoding="utf-8" .
在读取文本文件的时候,如果open()函数没有声明他们如何编码,python3会选取代码所运行的计算机操作系统的默认编码作为open()函数的编码方式。
windows10大陆区域为简体中文,可在cmd命令行输入“chcp”查看代码页:
或者:
而936代表的就是GBK简体中文。所以我的open()函数默认的编码为GBK。
但是改后对文件进行覆盖写(r+表示可读写,光标在文件开头),有时也会出错。
如:test02.txt文件删除之前的"你好",输入中英混合的:"hello中国"
再对其进行覆盖写:
也会报错!分析一下:
hello中国的utf8 16进制表示为:
68 65 6C 6C 6F E4 B8 AD E5 9B BD
天青色的utf8 16进制表示为:
E5 A4 A9 E9 9D 92 E8 89 B2
覆盖写入天青色后变成:
E5 A4 A9 E9 9D 92 E8 89 B2 9B BD
还剩两个字节 9B BD找不到对应的字符,自然就报错了:
如果把之前的"hello中国"改为"helloo中国",则会被"天青色"替换掉只剩下一个"国"了。
追加写或清空写不会出现这种报错。
-----------------------------------------------------------------------华丽的分割线-----------------------------------------------------------------------------
关于编码知识的补充:
1. 单字节的字符,字节的第一位设为0,如英文字母,UTF-8码只占用一个字节,和ASCII码完全相同;
2. n个字节的字符(n>1),如中文汉字,第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。
U+ 0000 ~ U+ 007F: 0XXXXXXX 单字节
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX 双字节
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX 三字节
U+10000 ~ U+10FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX 四字节
举个栗子,如:汉字里的“汉”字的Unicode编码16进制表示为:U+6C49(它占两个字节,6C是一个字节,49是一个字节。一个字节占8比特位,6是第一个八位的前4位0110),写成二进制是: 0110 1100 0100 1001。因为0x6C49在0x0800-0xFFFF之间, 使用3字节模板: 1110xxxx 10xxxxxx 10xxxxxx。我们只需要将 0110 1100 0100 1001 这个二进制数依次代替模板中的x,得到:
11100110 10110001 10001001, 转为16进制即E6 B1 89。这个就是被存到计算机中的字节序列。
查看字符编码的网站地址:http://www.mytju.com/classcode/tools/encode_utf8.asp
utf-8虽然国际通行,但是用三个字节表示一个汉字还是有点浪费空间。如果在简体中文环境下,使用gbk编码比utf-8更香。gbk是变长编码,占1个或者2个字节,1个字节时与ASCII码完全相同。