盒子
盒子
文章目录
  1. 1. 从硬盘到内存
    1. 读取一般文件(相对Python源代码文件来说的)
    2. Python源代码文件
  2. 2. 内存中,Python怎样进行操作的?
  3. 3. One more thing
  4. 4. 关于print输出问题
  5. 5. 参考资料

编码?你需要知道的那些事:3-Python2.x为什么有乱码

这是之前写的文章,因为博客地址换了,重新发布下。

这个系列目前一共四篇文章,之后可能会增加Python3.x关于编码方面的内容。

编码?你需要知道的那些事:1-了解字符集

编码?你需要知道的那些事:2-明白编码

编码?你需要知道的那些事:3-Python2.x为什么有乱码

编码?你需要知道的那些事:4-Java为什么有乱码

更新:

关于Python3 的编码

声明: Unicode是指字符集,不是编码

之前两篇文章,我们说到了字符集和编码,如果你还不知道字符集和编码的区别,可以先去看看前两篇文章或者Wiki下。

接下来的两篇文章我们准备讨论了Python下和Java下的乱码问题,本文我们先讨论Python平台(注意,以下提到的Python均为2.X版本),如果你只准备看Java的,那我加油写。哈哈!

让我们回到问题上来,为什么会有乱码呢? 不知道你发现没有,通常乱码都是出现了我们要进行I/O转换时(比如,从硬盘到内存)。下面我们就来讨论下这几个容易出现乱码的地方。

1. 从硬盘到内存

读取一般文件(相对Python源代码文件来说的)

首先,我们看看系统默认的文件编码(声明使用的是Python2.x版本,3.x默认是’utf-8’

1
2
3
4
#打开iPython,输入命令
In [1]: import sys
In [2]: sys.getdefaultencoding()
Out[2]: 'ascii'

太可怕了,上次不是说Windows中文的默认编码是GBK嘛,骗人,骗人!先别急,我们慢慢说。我们从记事本说起。打开记事本,输入’A’,然后保存。

对,它现在使用ASCII编码(看起来是),保存的。在Python下读取也没有问题。

1
2
3
4
5
In [3]: f = open('Demo.txt')
In [4]: s = f.read()
In [5]: print s
A
#正常显示

我们继续,这次我们再输入’中’字,如果真是ASCII编码的话,应该是不能正常显示的。

but , 它还是正常显示的。(得,这下招牌砸了…),不管了。我们再用Python读取看看(是不是它也像记事本一样神奇)。

1
2
3
4
In [3]: f = open('Demo.txt')
In [4]: s = f.read()
In [5]: print s
A ��

终于看见乱码了,不对啊,怎么一个正常一个乱码呢?不怕,我们再用Notepad++打开看看。

啊啊,不干啦,又是看不懂的。啥子情况呢?这时我们点击Encoding,选择GB2321(GBK的弟弟,他们的关系可以看看前两篇文章或者网上查查,因为Notepad++上没有GBK选项),这时奇迹出现了,抱头痛哭啊。

难道真是GBK编码吗? 我们用Python验证一下。(这里使用codes包,下文还会提到)

1
2
3
4
5
In [6]: import codecs #可以指定编码
In [7]: f = codecs.open('Demo.txt', encoding='GBK') #以GBK方式打开,用GB2312也行,为什么呢?不告诉你^_^
In [8]: s = f.read()
In [9]: print s
A 中

现在知道了吧? **其实Windows(中文)默认的文件编码是GBK的(**将到Java时也会验证这一点)。还记得我们讨论GBK时,知道它是兼容ASCII的(反正只是加了汉字进去)。所以它可以输入和显示中文(英文正常显示是必须的,世界上几乎所有的编码都能正常显示英文,我们可不一样…)。

最后,不要被它的外表骗了,我想Python去获取系统文件编码的时候可能就被骗了,当我加入中文时,却还用ASCII编码方式去读取文件,只能是乱码了。(其实,这里说的有点不对,Python2.x默认的编码就是’ASCII’,哈哈)

文件保存时用的什么编码,我们最好也用什么编码去读取。(为什么不说必须呢,因为有些编码是兼容的,比如UTF-8就兼容ASCII呢)。用记事本也没什么不可以,只要编码指定正确就好了。

Python源代码文件

Python源代码文件一般不用我们读取,主要是由Python解释器来读取执行。所以如果它读出来是乱码,咋们的程序就别想运行了。

还记得这句话吗,就是告诉解释器,读取该文件时请用UTF-8。(如果你文件全是字母的话倒不用,可我们不是还要写中文吗…),如果不声明,解释器将按照默认的编码读取(Python2.x是ASCII, Python3是UTF-8)。

1
# -*- coding: utf-8 -*-

上面说的是读取文件时要注意的,同样,从内存中写入文件也要注意编码指定要一致(一致最好)。不能说内存中是’UTF-8’或在’UTF-16’编码的,你却用ASCII去保存,那就有你好看了。(如果在内存中编码为’UTF-16’,一般也是用’UTF-8’保存,不过保存前先做转换,UTF-8常用些)。

2. 内存中,Python怎样进行操作的?

如果用Python2.x读取多个文件,有’UTF-8’、’GBk’编码,我们要将这些文件中的内容合并,然后保存的一个新文件中,那应该怎么做呢?不管, 先将他们读进来再说。

1
2
3
4
5
6
7
8
9
10
11
In [10]: f1 = open('Demo1.txt') #f1中只有一个'A' ,记事本默认保存方式
In [11]: f2 = open('Demo2.txt') #f2中有一个'A' 和'中' ,记事本默认保存方式
In [12]: f3 = open('Demo3.txt') #f3中有一个'A' 和'中' ,选择'UTF-8'保存
In [13]: s1 = f1.read()
In [14]: s2 = f2.read()
In [15]: s3 = f3.read()

现在要将他们合并,然后保存。不过,等等,就这样将他们连接起来,他们的编码方式不兼容啊!保存时我们应该选择什么编码保存呢?这是个问题。

1
2
3
4
5
In [16]: f4 = open('Demo4.txt', 'w') #打开新文件
In [17]: f4.write(s1 + s2 + s3) #保存
In [18]: f4.close()

这时打开文件(就用记事本,吓我一跳,这是什么啊!可以看到前两个文件保存正确,但是第三文件的内容不能正常显示)。

我们知道的是,记事本再神奇也没有这么神奇,知道前面用GBK,后面用UTF-8…
所以保存之前,我们可以将他们转换为同一种编码再保存。我们先看看Python2中str与unicode的关系。(str是编码后的;Unicode是未经编码的,其实它就是字符集)

1
2
3
4
5
6
7
(Python2.x str与unicode的关系)
<type 'basestring'>
|
+--<type 'str'>
|
+--<type 'unicode'>

原来str,和unicode都是basestring的孩子,由于Python的出现比Unicode字符集出现早,是后来才支持Unicode的。这里要知道的是,Unicode不是一种编码噢,一个Unicode字符在内存中的值就是在Unicode字符集编号的值,(这里还要说一下的就是,记事本中的unicode保存实际是按照UTF-16编码保存的,因为当初Unicode字符集使用16位,而UTF-16也是最小使用16位,所以容易搞错,要记住,Unicode仍然不是指编码)。这里我们就要使用decode方法,意思就是解码,我们先看看decode是什么意思。

1
2
3
4
5
s.decode(encoding) #将str解码为Unicode
<type 'str'> to <type 'unicode'>
u.encode(encoding) #将Unicode编码str
<type 'unicode'> to <type 'str'>

这样是不是要方便些呢, 操作是先将str解码为Unicode的,进行操作,待操作完成,我们就可以按照我们想要的编码方式保存啦!(不过,最好还是选择UTF-8保存)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [19]: s1 = s1.decode('GBK') #解码 ,这里用GBK, 虽然只有'A'
Out[19]: u'A'
In [20]: s1 = s2.decode('GBK') #解码
Out[20]: u'A \u4e2d'
In [21]: s3 = s3.decode('UTF-8') #解码
Out[21]: u'\ufeffA \u4e2d\n'
In [22]: s4 = s1 + s2 + s3 #连接 , 现在s4也为unicode
In [23]: s4 = s4.encode('UTF-8') #用'UTF-8'编码
In [24]: f4 = open('Demo4.txt', 'w') #打开新文件
In [25]: f4.write(s4) #保存编码后的字符串
In [26]: f4.close()

下面就是见证奇迹的时刻: (你没有看错,正常显示,最后保存用的是UTF-8)。

你可能说,要不要这么麻烦啊,其实是可以滴,Python提供了codecs,方便我们进行Unicode与str之间的编码和解码操作,下面来演示上面的例子。

1
2
3
4
5
6
7
8
9
10
11
In [27]: s1 = codecs.open('Demo1.txt', encoding='GBK').read()
In [28]: s2 = codecs.open('Demo2.txt', encoding='GBK').read()
In [29]: s3 = codecs.open('Demo3.txt', encoding='UTF-8').read()
In [30]: f7 = codecs.open('Demo7.txt', 'w', encoding='UTF-8')
In [31]: f7.write(s1+s2+s3)
In [32]: f7.close() #结果和上面一样,正常显示,最后保存用的是UTF-8

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对其进行编码了。我们来验证一下。

1
2
3
4
5
6
7
8
In [33]: import sys
In [34]: sys.stdout.encoding
Out[34]: 'UTF-8'
In [35]: print u'\u2713' #Python帮我们编码了

最后在附上一张图(证明上面使用的是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

支持一下
扫一扫,支持forsigner