郭姐姐 發表於 2019-7-16 03:03:00

关于python内open函数encoding编码问题

<p><span style="font-family: 隶书; color: rgba(0, 0, 0, 1)">在学python3.7的open函数时,我发现在pycharm里新建一个file_name.txt文本文件,输入中文保存,再用open(file_name,'r+')打开,再去读写时出现了一些小问题,记录一下。</span></p>
<p><strong>场景1</strong>:</p>
<p>c用“w”模式新建一个不存在的文件test01.txt,并写入你好:</p>
<p>&nbsp;<img src="https://img2022.cnblogs.com/blog/1742066/202211/1742066-20221124121151293-840516752.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;运行后再手动打开该文件:</p>
<p><img src="https://img2022.cnblogs.com/blog/1742066/202211/1742066-20221124121508312-131238228.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;发现乱码。</p>
<p>&nbsp;</p>
<p><strong>场景2</strong>:</p>
<p>我在项目里手动新建一个test02.txt,里面写入“你好”两个汉字保存,再open读取打印:</p>
<pre><span style="font-family: 隶书; font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1742066/202211/1742066-20221124122235921-1723732926.png" alt="" loading="lazy"><br><br><span style="font-family: 隶书; font-size: 15px">结果为:</span></span></pre>
<pre><span style="font-family: 隶书; font-size: 15px"><img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200412032336386-685491303.png" alt=""></span></pre>
<p>为什么是 " 浣 犲 ソ " 这三个陌生的玩意呢?查看“你好”的16进制表示:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200412032307941-678187881.png" alt=""></p>
<p>&nbsp;</p>
<p>再查看" 浣 犲 ソ "的GBK编码16进制表示:</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200412032511357-853033226.png" alt=""></p>
<p>好像明白了:open函数用GBK编码规则解码了被UTF-8编码规则编码的test02.txt文件。前者用两个字节表示一个汉字而后者用三个。</p>
<p>可直接用python验证这一点(encode默认utf8):</p>
<p><img src="https://img2020.cnblogs.com/blog/1742066/202109/1742066-20210902051049765-1005139040.png" alt="" loading="lazy"></p>
<p>&nbsp;而在场景1中恰恰相反,新建写入的时候用的是GBK,而手动打开查看的时候却用utf-8来加载,所以乱码了。</p>
<p>&nbsp;</p>
<p>把“你好”换成“中国”再试一次:报错了!</p>
<p><img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200413031230512-1303554478.png" alt=""></p>
<p><span style="background-color: rgba(255, 102, 0, 1)">注意:E4是位置0,AD是位置2</span></p>
<p>这是因为汉字“中国”的6字节编码中的第3、4两个字节ADE5可能没有对应的GBK编码字符,从而导致出错。</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200412033222231-438174811.png" alt=""></p>
<p>&nbsp;</p>
<p><span style="font-size: 16px; color: rgba(255, 102, 0, 1)"><strong>解决方法:</strong></span></p>
<p><span style="font-family: 隶书; font-size: 15px; color: rgba(255, 102, 0, 1)">申明open()函数的编码方式为'utf-8',即encoding="utf-8" .</span></p>
<p><span style="font-family: 隶书; font-size: 15px; color: rgba(51, 153, 102, 1)">在读取文本文件的时候,如果open()函数没有声明他们如何编码,python3会选取代码所运行的计算机操作系统的默认编码作为open()函数的编码方式。</span></p>
<p>&nbsp;windows10大陆区域为简体中文,可在cmd命令行输入“chcp”查看代码页:</p>
<p><img src="https://img2020.cnblogs.com/blog/1742066/202109/1742066-20210903013721516-1084701782.png" alt="" loading="lazy"></p>
<p>或者:</p>
<p><img src="https://img2020.cnblogs.com/blog/1742066/202109/1742066-20210903024258271-878642698.png" alt="" loading="lazy"></p>
<p>而936代表的就是GBK简体中文。所以我的open()函数默认的编码为GBK。</p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1742066/202109/1742066-20210903013944949-1122792411.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>但是改后对文件进行覆盖写(r+表示可读写,光标在文件开头),有时也会出错。</p>
<p>如:test02.txt文件删除之前的"你好",输入中英混合的:"hello中国"</p>
<p><img src="https://img2022.cnblogs.com/blog/1742066/202211/1742066-20221124131541361-401423430.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>再对其进行覆盖写:</p>
<p><img src="https://img2022.cnblogs.com/blog/1742066/202211/1742066-20221124130352941-1546848262.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>也会报错!分析一下:</p>
<p><span style="color: rgba(255, 102, 0, 1)"><strong>hello中国</strong></span>的utf8 16进制表示为:</p>
<p><span style="color: rgba(255, 102, 0, 1)">68 65 6C 6C 6F</span>&nbsp; &nbsp; <span style="color: rgba(255, 102, 0, 1)">&nbsp;<span style="color: rgba(0, 255, 0, 1)">E4 B8 AD</span></span>&nbsp; &nbsp; <span style="color: rgba(0, 0, 255, 1)">E5 9B BD</span></p>
<p><strong><span style="color: rgba(255, 102, 0, 1)">天青色</span></strong>的utf8 16进制表示为:</p>
<p><span style="color: rgba(255, 102, 0, 1)">E5 A4 A9</span>&nbsp;&nbsp;<span style="color: rgba(0, 255, 0, 1)">E9 9D 92</span>&nbsp;&nbsp;<span style="color: rgba(0, 0, 255, 1)">E8 89 B2</span></p>
<p><br>覆盖写入<strong><span style="color: rgba(255, 102, 0, 1)">天青色</span></strong>后变成:</p>
<p><span style="color: rgba(255, 102, 0, 1)">E5 A4 A9</span>&nbsp;&nbsp;<span style="color: rgba(153, 204, 0, 1)"><span style="color: rgba(0, 255, 0, 1)">E9 9D 92</span>&nbsp;</span>    <span style="color: rgba(51, 102, 255, 1)">E8 89B2&nbsp;</span>    <span style="color: rgba(255, 0, 0, 1)">9B BD</span></p>
<p>还剩两个字节 <span style="color: rgba(255, 0, 0, 1)">9B BD</span>找不到对应的字符,自然就报错了:</p>
<p><img src="https://img2020.cnblogs.com/blog/1742066/202004/1742066-20200412035702275-397189308.png" alt="" style="font-family: &quot;Courier New&quot;"></p>
<p>如果把之前的"hello中国"改为"helloo中国",则会被"天青色"替换掉只剩下一个"国"了。</p>
<p>追加写或清空写不会出现这种报错。</p>
<p>&nbsp;</p>
<p><span style="color: rgba(255, 0, 0, 1)">&nbsp;-----------------------------------------------------------------------华丽的分割线-----------------------------------------------------------------------------</span></p>
<p><span style="color: rgba(255, 0, 0, 1)">关于编码知识的补充:</span></p>
<div>  <span style="color: rgba(0, 128, 0, 1)">所谓的Unicode编码其实是字符集和编码方式(utf8、utf16、utf32)以及其他属性的总称。Unicode标准把全球的字符用唯一的16进制编号表示出来,这个编号就叫“码点”或“码位”(Code Point),如U+708E表示汉字“炎”。所有码点共占21个bits(一开始占16个bits,2字节,后来不够用有所升级),范围是0 ~ 1 0000 1111 1111 1111 1111&nbsp;,即0x0~0x10FFFF,最多可表示1114111个。在最新的Unicode13.0版本中分配了14万多个码点。注意,在0x0~0x10FFFF范围内的任何值都是码点,但不是所有的码点都有对应的字符,如基本多语言平面中0xD800~0xDFFF这段范围就没有对应的字符,这里面是保留给utf-16编码的高位代理和低位代理。</span></div>
<div>&nbsp;</div>
<div><span style="color: rgba(0, 128, 0, 1)">  码元(Code Unit,也称“代码单元”)是指一个已编码的文本中具有最短的比特组合的单元。对于UTF-8来说,码元是8比特长;对于UTF-16来说,码元是16比特长。</span></div>
<div>&nbsp;</div>
<div><span style="color: rgba(0, 128, 0, 1)">  Unicode将每16位二进制数表示的范围作为一个平面,第一个平面称为基本多语言平面,用于与常用字符对应,其范围是&nbsp;0000 0000 0000 0000 ~ 1111 1111 1111 1111,用十六进制表示为&nbsp;0x0000 ~ 0xFFFF&nbsp;;剩余十六个平面称为辅助平面,与一些辅助字符对应,如中日韩表意文字,emoji表情,甲骨文等。</span></div>
<div>&nbsp;</div>
<div><span style="color: rgba(0, 128, 0, 1)">  Unicode没有规定具体怎么将字符存储到计算机硬盘中。而UTF-8就是具体编码的体现,是将码点按8比特长码元转化为01010101字节序列的一套编码规则。</span></div>
<div>&nbsp;</div>
<div><span style="color: rgba(0, 128, 0, 1)">  其实当初我看到这句话的时候我是有点疑惑的!我们经常说Unicode编码,那我们为什么不能直接将Unicode字符集里与字符对应的码点以字节序列的形式存到计算机里面呢?就像ASCII编码那样直接用数字65来存储大写字母“A"?</span></div>
<div>&nbsp;</div>
<div><span style="color: rgba(0, 128, 0, 1)">  其实可以,那就是utf-32编码。因为Unicode字符集里的码点总共才占了21比特位,我用3字节24比特位去存绰绰有余,但是考虑到CPU的寄存器位数是2的n次方,所以直接用4字节32比特位来存。但这对于原本只需一个字节的英文或数字字符现在都要四个字节,其中三个字节都填充了0,占用内存太大,实在是浪费资源。后来用utf-16,用两个字节或四个字节来存储,不定长的,或者用ucs-2定长的2字节来存储,都不太令人满意,其中还涉及到字节序的问题。几经折腾还是觉得utf-8比较香。</span></div>
<div><span style="color: rgba(0, 128, 0, 1)">&nbsp;</span></div>
<div><span style="color: rgba(0, 128, 0, 1)">  UTF-8 是一种不定长的Unicode 编码方式,一个字符可能占用1个字节,也有可能占用2,3,4 个字节。那系统怎么知道对什么字符采取几个字节进行编码呢?是有规则的。</span></div>
<div><span style="color: rgba(0, 128, 0, 1)">  Unicode字符集里的码点转化成utf-8编码的多字节序列过程:</span></div>
<div>
<p><span style="color: rgba(255, 102, 0, 1)">  1. 单字节的字符,字节的第一位设为0,如英文字母,UTF-8码只占用一个字节,和ASCII码完全相同;</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">  2. n个字节的字符(n&gt;1),如中文汉字,第一个字节的前n位设为1,第n+1位设为0,后面字节的前两位都设为10,这n个字节的其余空位填充该字符unicode码,高位用0补足。</span></p>
<div>
<p><span style="color: rgba(255, 102, 0, 1)">  U+ 0000 ~ U+ 007F:   0XXXXXXX&nbsp;  单字节</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">  U+ 0080 ~ U+ 07FF:   110XXXXX 10XXXXXX  双字节</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">  U+ 0800 ~ U+ FFFF:   1110XXXX 10XXXXXX 10XXXXXX&nbsp;  三字节</span></p>
<p><span style="color: rgba(255, 102, 0, 1)">  U+10000 ~ U+10FFFF:  11110XXX 10XXXXXX 10XXXXXX 10XXXXXX  四字节</span></p>
<div><span style="color: rgba(0, 128, 0, 1)">UTF-8 解码规则也很简单。如果一个字节的第一位是0,则这个字节单独就表示一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。</span></div>
<div>&nbsp;</div>



























</div>



























</div>
<p><span style="color: rgba(51, 153, 102, 1)">举个栗子,如:汉字里的“汉”字的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,得到:</span></p>
<p><span style="color: rgba(51, 153, 102, 1)">11100110 10110001 10001001, 转为16进制即E6 B1 89。这个就是被存到计算机中的字节序列。</span></p>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1742066/202101/1742066-20210131012232979-1215915272.png" alt="" loading="lazy"></p>
<p>查看字符编码的网站地址:<span style="text-decoration: underline"><span style="color: rgba(0, 128, 0, 1); text-decoration: underline"><span style="color: rgba(0, 128, 0, 1); text-decoration: underline">http://www.mytju.com/classcode/tools/encode_utf8.asp</span></span></span></p>
<div>
<div>UTF-8编码有很多优点:</div>
<div>1、存储文本文件到计算机硬盘节省存储空间</div>
<div>2、传输字符串数据时,节省宽带</div>
<div>3、编码是变长的,很好的兼容了ASCII</div>
<div>4、容错能力强悍。哪怕字节序列损坏,它也能够跳过损坏部分,进而正确解码后面的字符。</div>
<div>但是21-bits的code point与8-bits为一个code unit的字节序列之间的转换需要更多系统开销。编码速度相对要慢一些。</div>



























</div>
<p>utf-8虽然国际通行,但是用三个字节表示一个汉字还是有点浪费空间。如果在简体中文环境下,使用gbk编码比utf-8更香。gbk是变长编码,占1个或者2个字节,1个字节时与ASCII码完全相同。</p>
<pre><span style="font-size: 14px">&nbsp;</span></pre><br><br>
来源:https://www.cnblogs.com/wangyi0419/p/11192593.html
頁: [1]
查看完整版本: 关于python内open函数encoding编码问题