解决python的中文字符编码问题

本文原载于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编码格式下的二进制表示。

这里就出现了两个问题:

  1. 变量s赋值时,一段中文字符串是怎么变成二进制字节流的?
  2. 打印变量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
2
3
4
5
6
7
8
9
# -*- coding:utf-8 -*-

s = "这是一段中文" # s是str类型的变量,计算机把字符串以utf-8格式编码成二进制字节,赋值给s

s.decode("utf-8") # 二进制字节s以utf-8格式解码到unicode,解码后s从str类型变为unicode类型

s.encode("GBK") # unicode类型的变量s被以GBK格式编码为二进制字符串,编码后变量s从unicode类型变为str类型

# 程序输出:一段乱码

反过来,要把一个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
2
import 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. 检验学习成果

python常见编码错误集合 - 妙音的博客

看看上面链接的博客里所列举的几个错误示例,现在你是否能够一眼就找出错误点,并给出解决方法呢?

0%