这是之前写的文章,因为博客地址换了,重新发布下。
这个系列目前一共四篇文章,之后可能会增加Python3.x关于编码方面的内容。
编码?你需要知道的那些事:3-Python2.x为什么有乱码
更新:
声明: Unicode是指字符集,不是编码
之前两篇文章,我们说到了字符集和编码,如果你还不知道字符集和编码的区别,可以先去看看前两篇文章或者Wiki下。
接下来的两篇文章我们准备讨论了Python下和Java下的乱码问题,本文我们先讨论Python平台(注意,以下提到的Python均为2.X版本),如果你只准备看Java的,那我加油写。哈哈!
让我们回到问题上来,为什么会有乱码呢? 不知道你发现没有,通常乱码都是出现了我们要进行I/O转换时(比如,从硬盘到内存)。下面我们就来讨论下这几个容易出现乱码的地方。
1. 从硬盘到内存
读取一般文件(相对Python源代码文件来说的)
首先,我们看看系统默认的文件编码(声明使用的是Python2.x版本,3.x默认是’utf-8’)
|
|
太可怕了,上次不是说Windows中文的默认编码是GBK嘛,骗人,骗人!先别急,我们慢慢说。我们从记事本说起。打开记事本,输入’A’,然后保存。
对,它现在使用ASCII编码(看起来是),保存的。在Python下读取也没有问题。
|
|
我们继续,这次我们再输入’中’字,如果真是ASCII编码的话,应该是不能正常显示的。
but , 它还是正常显示的。(得,这下招牌砸了…),不管了。我们再用Python读取看看(是不是它也像记事本一样神奇)。
|
|
终于看见乱码了,不对啊,怎么一个正常一个乱码呢?不怕,我们再用Notepad++打开看看。
啊啊,不干啦,又是看不懂的。啥子情况呢?这时我们点击Encoding,选择GB2321(GBK的弟弟,他们的关系可以看看前两篇文章或者网上查查,因为Notepad++上没有GBK选项),这时奇迹出现了,抱头痛哭啊。
难道真是GBK编码吗? 我们用Python验证一下。(这里使用codes包,下文还会提到)
|
|
现在知道了吧? **其实Windows(中文)默认的文件编码是GBK的(**将到Java时也会验证这一点)。还记得我们讨论GBK时,知道它是兼容ASCII的(反正只是加了汉字进去)。所以它可以输入和显示中文(英文正常显示是必须的,世界上几乎所有的编码都能正常显示英文,我们可不一样…)。
最后,不要被它的外表骗了,我想Python去获取系统文件编码的时候可能就被骗了,当我加入中文时,却还用ASCII编码方式去读取文件,只能是乱码了。(其实,这里说的有点不对,Python2.x默认的编码就是’ASCII’,哈哈)
文件保存时用的什么编码,我们最好也用什么编码去读取。(为什么不说必须呢,因为有些编码是兼容的,比如UTF-8就兼容ASCII呢)。用记事本也没什么不可以,只要编码指定正确就好了。
Python源代码文件
Python源代码文件一般不用我们读取,主要是由Python解释器来读取执行。所以如果它读出来是乱码,咋们的程序就别想运行了。
还记得这句话吗,就是告诉解释器,读取该文件时请用UTF-8。(如果你文件全是字母的话倒不用,可我们不是还要写中文吗…),如果不声明,解释器将按照默认的编码读取(Python2.x是ASCII, Python3是UTF-8)。
|
|
上面说的是读取文件时要注意的,同样,从内存中写入文件也要注意编码指定要一致(一致最好)。不能说内存中是’UTF-8’或在’UTF-16’编码的,你却用ASCII去保存,那就有你好看了。(如果在内存中编码为’UTF-16’,一般也是用’UTF-8’保存,不过保存前先做转换,UTF-8常用些)。
2. 内存中,Python怎样进行操作的?
如果用Python2.x读取多个文件,有’UTF-8’、’GBk’编码,我们要将这些文件中的内容合并,然后保存的一个新文件中,那应该怎么做呢?不管, 先将他们读进来再说。
|
|
现在要将他们合并,然后保存。不过,等等,就这样将他们连接起来,他们的编码方式不兼容啊!保存时我们应该选择什么编码保存呢?这是个问题。
|
|
这时打开文件(就用记事本,吓我一跳,这是什么啊!可以看到前两个文件保存正确,但是第三文件的内容不能正常显示)。
我们知道的是,记事本再神奇也没有这么神奇,知道前面用GBK,后面用UTF-8…
所以保存之前,我们可以将他们转换为同一种编码再保存。我们先看看Python2中str与unicode的关系。(str是编码后的;Unicode是未经编码的,其实它就是字符集)
|
|
原来str,和unicode都是basestring的孩子,由于Python的出现比Unicode字符集出现早,是后来才支持Unicode的。这里要知道的是,Unicode不是一种编码噢,一个Unicode字符在内存中的值就是在Unicode字符集编号的值,(这里还要说一下的就是,记事本中的unicode保存实际是按照UTF-16编码保存的,因为当初Unicode字符集使用16位,而UTF-16也是最小使用16位,所以容易搞错,要记住,Unicode仍然不是指编码)。这里我们就要使用decode方法,意思就是解码,我们先看看decode是什么意思。
|
|
这样是不是要方便些呢, 操作是先将str解码为Unicode的,进行操作,待操作完成,我们就可以按照我们想要的编码方式保存啦!(不过,最好还是选择UTF-8保存)
|
|
下面就是见证奇迹的时刻: (你没有看错,正常显示,最后保存用的是UTF-8)。
你可能说,要不要这么麻烦啊,其实是可以滴,Python提供了codecs,方便我们进行Unicode与str之间的编码和解码操作,下面来演示上面的例子。
|
|
3. One more thing
要在Python避免乱码,我们需要记住:
- Decode early #可以自己decode,也可以使用codes.open
- Unicode everywhere #操作时,一律unicode
- Encode late #要保存是,记得编码,可以用encode,或者codes.open
所以,最好的情况还是用文件保存的编码打开(因为不确定是否为UTF-8编码的),然后解码为Unicode,进行操作,然后保存。(下面我以UTF-8文件为例子,有图有真相_)。
4. 关于print输出问题
关于为什么是Unicode,我们用print输出时我们可以看见正确的字符呢?按理来说我们应该先对其进行编码才行呢?其实,在调用print输出时,Python已经按照UTF-8对其进行编码了。我们来验证一下。
|
|
最后在附上一张图(证明上面使用的是Unicode的编号,可以在这里查找):
5. 参考资料
Java中的字符集编码入门(五)Java代码中的字符编码转换Part 1
Unicode In Python, Completely Demystified
python string encode / decode
Python字符串和编码
why-does-python-print-unicode-characters-when-the-default-encoding-is-ascii