一. 概述:
编码问题对程序员来说,似乎一直都是非常头疼的问题。这其中的原因是历史遗留问题。
最开始,都没有语言的概念,只有字符编码集ASCII码。因为发明这些的人都是用英语的,他们根本没考虑语言问题。
后来,不同语言也有了各自的编码,比如英文是latin1(别名ISO8859-1, en_US),简体中文是gbk(cp936,以及扩展字符集gb18030,还有早期更小的gb2312),繁体中文是BIG5。这样涉及多种语言的代码就太难了。
再后来,终于有了兼容所有语言的编码方案utf-8,可惜各种编码各自为政的时间已经太久了,utf-8一统天下,彻底消除编码苦恼的日子暂时还遥遥无期。
具体到一个程序员的日常应用细节,某个场景可能是这样的:
一位中国程序员,他的工作电脑是win10系统,系统默认编码字符集是gbk,他最常用的软件pycharm设定字符集是utf-8。而他管理和工作的服务器操作系统是centos7,系统默认编码是latin1。为了减少乱码带来的困惑,他已经尽可能地在更多的地方设定字符集是utf-8。然而,不能把所有的地方都设定为utf-8,因为还有大量开发于远古时代并且一直沿用到今天的工具,仅仅支持latin1。如果闭着眼睛把编码设定为utf-8,可能某个很深层的第三方库返回的结果完全是错误的,而对这种莫名其面的错误你可能永远都不会想到是编码带来的。
那么,我们可以怎么做呢?在此之前先了解一下编码系统的工作原理。
二. 计算机编码介绍
两句话总结:
众所周知,计算机能处理和存储的最终都是二进制数。字符集的作用就是在人类日常用的文字和计算机处理的二进制数之间建立一一对应的关系。比如原始的ASCII码和万能的unicode码。按照国际标准化组织的规定,现在计算机程序统一使用unicode码表示字符,因此unicode码和字符是等价的。我们提到字符和字符串,就是指其unicode码。
另一方面,为了让字符的unicode码在存储的时候更节约空间,传输的时候更节约带宽,计算的时候更节约cpu和内存,对不同类型和不同应用场景下又规定了unicode码的存储方式,也就是对unicode码进行二次编码,比如latin1,gbk,utf-8,utf-16等等。这才是我们日常所说的编码和解码,编码指由unicode码到特定的二进制存储,解码指由特定的二进制存储到unicode码。
进一步地:
文件读写是一对编码解码过程,其中写入文件是编码过程,读取是解码过程
系统的I/O也是一对编码解码过程,其中print 是编码过程,终端显示是适配print的解码过程。
从文件到屏幕一共经历写入,读取,输出,显示,共两对四个编码解码过程,每对编码和解码要分别匹配才能不出问题
参考文档:
https://www.zhihu.com/question/23374078
https://www.cnblogs.com/wmars/p/8572646.html
https://blog.csdn.net/onepiecehuiyu/article/details/75208743
https://www.cnblogs.com/tsingke/p/10853936.html
三. 应用举例:
1.终端 与 print
print的默认编码方式是继承系统IO的编码方式sys.stdout.encoding:
因此,终端的解码方式要和IO的编码方式设置相同。
python中IO的编码设定方式:sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='gb18030')
windows下cmd终端的设置:chcp 65001,chcp 936
secureCRT和linux自带终端的编码设置方式类似。
centos 默认I/O编码 latin1 ,终端默认编码latin1,secureCRT默认编码 gbk
2.open 与 write
open的解码方式与 write的编码方式默认继承系统的文件系统编码方式 locale.LC_ALL的default设置,python用locale.getpreferredencoding():
open的解码码方式要与write的编码方式一致。未知编码方式的文件open时只能挨个尝试。
Popen 中设定文件写入编码是无效的,open中才有效。
无效:
subprocess.call("echo '扯淡' ", shell=True, stdout=open("to", 'w', encoding='gbk'))
有效:
with open("ti", 'w', encoding='gbk') as fo:
fo.write('扯淡')
3. cat
特别注意:cat 直接将文件中的二进制字节流传送给终端,而不做任何处理,因此不依赖于任何系统编码方式,设定终端解码方式和文件编码方式一致即可正常工作。
文件和终端编码不一致时,也可以转码:cat test.txt | iconv -f GBK -t UTF-8
4. vim
vim 是一个庞大的软件,其编码也涉及好几个环节,下面详细地说一说。
1). 解码
vim首先用fileencodings设置猜测文件的编码,并用该编码解码
2). 转换为系统编码
解码之后,再转换为encoding指定的编码,该编码继承系统的编码,locale.LC_ALL
3). 输出为终端编码
转换为系统编码后,还会进一步转换为termencoding指定的编码,以输出到终端设备
termencoding默认为空,继承和encoding一样的系统编码LC_ALL
另外,vim自身的界面和提示都继承和encoding一样的系统编码LC_ALL
附:vim 设置相关,vim 的设置非常庞大,和编码有关的有:
名称 短名称 用处
fileencodings fencs vim会依次用该列表中的编码方式尝试解码文件,可能猜错
fileencoding fenc vim通过猜测后认定的当前文件编码方式,可能是错的
encoding enc 继承系统编码,决定vim界面和文件内容的真实编码方式
termencoding tenc vim输出到终端的编码,默认为空,继承系统编码
使用方式:
:set fencs 查看该参数
:set fencs=utf-8,gb18030,gbk 设置该参数
注意,这种设置方式只对当前vim打开的文件有效,需要对所有文件永久生效 要更改vim的配置文件vimrc设置,执行:
vim ~/.vimrc
写入如下配置并保存即可。
set fencs=utf-8,gb18030,gbk
举例(1):Linux下编辑utf-8代码文件
操作系统是cetos,系统默认编码latin1,终端编码gbk,用vim函数编辑一个utf-8编码的代码文件,中文部分会乱码。解决办法:
:set tenc=gb18030
:set enc=utf-8
一劳永逸使vim同时自动支持gbk和utf-8文件:
执行:vim ~/.vimrc,写入如下配置
set fencs=utf-8,gb18030,gbk
set tenc=gb18030
set enc=gbk
举例(2):
如果系统编码设置为utf-8,终端编码为gb18030,vim 一个gbk编码的文件,界面和内容都是乱码。
解决办法:
1.把系统编码设置为gbk,3者一致就解决问题了
2.不想改系统编码,可以强制转换文件为utf-8编码,并且设置termencoding为gb18030。当然,这时候文件本身显示正确,但是vim界面是乱码,这是个极端的例子。
iconv -f gbk -t utf8 file1 -o file2
3. 不想改系统编码,也不想改文件编码,可以修改文件读取的编码为gbk,并且设置termencoding为gb18030
:edit ++enc=gbk
或者
:set fencs=utf-8,gbk,gb18030,gb2312
:e
总结,vim的乱码问题一般是第1步文件编码猜测和第3步输出到终端造成的。对于第1步,可以指定正确的编码方式,或者给出可能的编码,重新载入文件时vim会重新猜测。对于第3步,保证终端编码和系统编码一致即可,这也是大部分情况。确实不一样,可以指定termencoding与终端编码一致
参考文档:
https://blog.csdn.net/paul313/article/details/46816009?locationNum=9
https://blog.csdn.net/smstong/article/details/51279810
5. linux下 python处理gbk文件的时的奇怪错误
一个常见的应用场景如下,操作系统是cetos,系统默认编码latin1,用python的open 函数读取一个gbk编码的文件,然后用print函数输出到编码设定为gbk的终端。一切都很完美!然而只要在程序中稍微增加点操作,比如读取到的字符串增加几个字符,进行数据库写入等等,马上就会发生奇怪的乱码问题。我们来梳理下整个过程:
首先,存储为gbk编码的文件,相当于对文本内容执行了.encode("gbk");
然后,open 函数调用系统默认locale.getpreferredencoding()解码,相当于.encode("gbk").decode("latin1"),注意,这里已经有明显的错误了,内存中存储的文本内容是错误解码的奇怪文本,gbk兼容latin1,因此英文和数字部分是正确的,中文部分全都是奇怪的乱码;
接下来,print 函数调用系统默认sys.stdout.encoding编码,相当于.encode("gbk").decode("latin1").encode("latin1");
最后,终端收到数据流,用gbk解码,相当于.encode("gbk").decode("latin1").encode("latin1").decode("gbk")。歪打正着,刚好形成一个闭环,所以表面上看起来没有问题。
总结,从文件到屏幕一共经历编码解码四个过程,编码和解码要成对匹配才能不出问题。如果需要把print之前的奇怪文本正确地发送给写库等操作,对其执行.encode("latin1").decode("gbk")即可刚好恢复出正确的文本。
特别是,这种情况下其实是把gbk错误解码成Latin1,然后用相关的shell工具处理文本。如果用gbk正确解码,发送给shell则会出现中文不能用latin1编码的问题。这时候可以用gbk编码后发送给shell(见下文:四. 1)。会不会也出现强制utf-8编码后字典的key找不到之类的奇怪问题呢?还有待于实践验证。直接把系统编码由latin1改为gbk或者utf-8即可完成设置.
四. 其他:
1. # Popen调用中文命令
subprocess.Popen(u'cd 我的文档'.encode('gbk'))
2. python console 编码乱码
有人说:
File-Setting-Search-console-python console
import os;os.system('chcp 65001')
经测试,设置无效,正确的办法是:
File | Settings | Editor | File Encodings的project encoding 设置为utf-8,这项等价于sys.stdout.encoding加上'chcp 65001'。永久生效,范围是:pycharm的run以及Python Console窗口。对Terminal窗口无效。Terminal窗口的默认print编码和终端编码都是utf-8。而sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='gb18030')的生效范围只是当前.py文件。
很难设为gbk,因为sys.getdefaultencoding()默认是utf-8,且不提供修改接口。
https://blog.csdn.net/huiyanshizhu/article/details/78907629
3. 设置系统编码的方式:
export LANG=zh_CN.UTF-8
export LC_ALL=en_US.UTF-8
4. 常见的乱码:
???? utf-8解码
鎵贰 gbk解码
?0?7 '弄'.encode("gb18030").decode("utf-8").encode("gb18030").decode("gbk")
? '弄'.encode("gbk").decode("utf-8")
备注:以上均为个人的一点理解,如果不妥,还望指正~