本文原载于https://imlogm.github.io,转载请注明出处~
摘要:最近在做自然语言处理相关的项目,发现中文编码的问题实在需要好好学习下,我用python为例,简单介绍下python编程时如何处理好中文编码的问题。
关键字:自然语言处理, 字符编码, python
1. 从字符编码谈起
讲真,字符编码是很大的一块内容,单用一篇博客是完全讲不完的。这里借用一下大佬的文章:字符编码笔记:ASCII,Unicode 和 UTF-8 - 阮一峰的日志
看完上面的那篇文章之后,相信你对字符编码有了一定的认识。在中文的自然语言处理中,最常遇到的是ASCII,Unicode,UTF-8,GB2312,GBK等。这几种编码,你都可以搜索相关的文章看下,我这里就不展开介绍了。直接用几个python的程序解释下如何在python中处理字符编码的问题。
2. 关于python的str类型和print过程
比如一段程序:1
2
3
4
5
6# -*- coding:utf-8 -*-
s = "这是一段中文" # s是str类型的变量
print(s)
# 程序输出:“这是一段中文”
这段程序中的变量s就是str类型的。我们都知道计算机内部都是二进制的0和1,str类型就是这样的0和1组成的二进制字节流,也就是说这里的变量s在计算机内部是一段二进制字节,并不是字符串。
如果你是在python的交互式编程环境中,那么你可以做个实验:1
2
3>>> s = "这是一段中文"
>>> s
\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe6\xae\xb5\xe4\xb8\xad\xe6\x96\x87
\x
表示这个数是十六进制数,\xe8
表示这个数是十六进制数“E8”,转换为二进制为11101000
,上面的“\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe6\xae\xb5\xe4\xb8\xad\xe6\x96\x87”就是变量s所代表的字节流,换句话说,就是字符串“这是一段中文”在utf-8编码格式下的二进制表示。
这里就出现了两个问题:
- 变量s赋值时,一段中文字符串是怎么变成二进制字节流的?
- 打印变量s时,二进制字节流是怎么变成一段中文字符串的?
首先是问题1。变量s赋值时,字符串经过某种编码方式编码(encode)成为二进制字节,再赋值给变量s。这里的“某种编码方式”由代码显式指出,代码的第一行# -*- coding:utf-8 -*-
就是用来显式地告诉计算机,你在str类型赋值时,用utf-8的编码方式。
然后是问题2。打印变量s时,二进制字节流通过某种编码方式解码(decode)为字符串。这里的“某种编码方式”由操作系统指出。我用的ubuntu系统使用的是utf-8的编码方式。
注意体会编码
和解码
这两个词的不同。编码方式和解码方式一样,才能正常print,否则显示的是乱码。
可能你还是不太明白,我们用上面的程序再做一组实验。因为每台电脑的命令行的编码方式不一样,我用的是ubuntu的系统,编码格式是utf-8,我以我的电脑为例来讲解。同时,注意实验要在命令行的状态下进行。有些ide比较智能,会自动更换输出环境的编码格式,达不到实验效果。
第一个程序和上面的一样,我们来看下效果:1
2
3
4
5
6
7
8
9# -*- coding:utf-8 -*-
# 命令行的编码方式为utf-8
s = "这是一段中文" # s是str类型的变量,计算机把字符串以utf-8格式编码成二进制字节,赋值给s
print(s) # s是str类型的变量,计算机读取s(也就是读取出二进制字节),然后以utf-8格式解码为字符串
# 程序输出:“这是一段中文”
然后,我们改动第一行:1
2
3
4
5
6
7
8
9# -*- coding:GBK -*-
# 命令行的编码方式为utf-8
s = "这是一段中文" # s是str类型的变量,计算机把字符串以GBK格式编码成二进制字节,赋值给s
print(s) # s是str类型的变量,计算机读取s(也就是读取出二进制字节),然后以utf-8格式解码为字符串
# 程序输出:一段乱码
我们再做第三个实验:1
2
3
4
5
6
7
8
9# -*- coding:utf-8 -*-
# 命令行的编码方式为GBK
s = "这是一段中文" # s是str类型的变量,计算机把字符串以utf-8格式编码成二进制字节,赋值给s
print(s) # s是str类型的变量,计算机读取s(也就是读取出二进制字节),然后以GBK格式解码为字符串
# 程序输出:一段乱码
我想你应该能理解这三个程序之间的区别。
2. 关于unicode类型
unicode类型是python中的一种字符串类型,在计算机内也是二进制字节。不过不同于str是单纯的二进制字节,unicode类型特指由ucs2或者ucs4编码格式编码的二进制字节。
如果你在python的交互式编程环境中,你可以做个实验:1
2
3>>> s = u"这是一段中文" # 这边多了个u,表示变量s为unicode变量
>>> s
u'\u8fd9\u662f\u4e00\u6bb5\u4e2d\u6587'
可以看到,和上面str类型的实验结果的\x
不一样了,这里出现的是\u
。\u
代表了在unicode编码表中的位置,比如\u8fd9
就代表unicode编码表中8fd9这个位置的字符。
python中unicode类型的变量是作为一个中转站存在的。比如你要把一段字符串从utf-8编码转为GBK编码,你需要做的是:
1 | # -*- coding:utf-8 -*- |
反过来,要把一个GBK编码的字符串转为utf-8也一样,要以unicode作为中转站。
3. sys.setdefaultencoding(‘utf-8’)
网上一些教程会教你,在遇到中文编码问题的时候,在代码的开头加上这几句:1
2
3
4# -*- coding:utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
你一试,还真的解决了问题,但你不知道这几句话有什么用。
第一句# -*- coding:utf-8 -*-
上一段已经说了,是为了显式地说明代码是由utf-8格式编码的,如果你不加的话,一般来说是采用默认编码ascii。ascii不支持中文,你的代码中有任何中文就会出错。
后面三句,最重要的是sys.setdefaultencoding('utf-8')
,它的目的是修改默认的解码方式为utf-8。
看下面的实验:1
2
3# -*- coding: utf-8 -*-
s = '中文字符' # s是字符串经过utf-8编码格式编码后的二进制字节,str类型
s.encode('GBK') # s是二进制字节,它不会直接encode。python会首先调用decode,将s从str类型变为unicode格式,再用GBK编码为str类型
你可以使用以下代码获取python默认的解码方式:1
2import sys
print(sys.getdefaultencoding())
假如你获取到的默认解码方式为ascii。那么:1
2
3
4
5
6# -*- coding: utf-8 -*-
s = "这是一段中文" # s是字符串经过utf-8编码格式编码后的二进制字节,str类型
s.encode("GBK") # s是二进制字节,它不会直接encode。python会首先调用decode,将s从str类型变为unicode格式,再用GBK编码为str类型
# 如果你的默认解码方式为ascii,那么上面一句话在实际执行时,相当于下面这句话
s.decode("ascii").encode("GBK")
显然,程序会报错:UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe4 in position 。
解决方法1,显示地指明解码格式:1
2
3
4# -*- coding: utf-8 -*-
s = "这是一段中文"
s.decode("utf-8").encode("GBK")
解决方法2,修改默认解码方式:1
2
3
4
5
6
7
8# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
s = "这是一段中文"
s.encode("GBK")
你应该能从这几个实验中明白sys.setdefaultencoding('utf-8')
的作用。
4. 检验学习成果
看看上面链接的博客里所列举的几个错误示例,现在你是否能够一眼就找出错误点,并给出解决方法呢?