编码/解码问题是个大坑,其中的复杂性,大多来自历史包袱

由于计算机领域的分层架构和多平台问题,这个问题被进一步加剧,unicode的出现,给这个问题带来了曙光.

可是生活不总是那么美好的,有些时候,一不小心,我们还是会掉到满是泥沼的坑里

周末在凤凰书城看一本数据清洗相关的书,其中说道噪声数据的问题,有问题的编码是噪声的来源之一,书中分享了不少好用的方法,在便签里记了一些,加上之前笔记里的,正好整理成一篇文章

编码问题

这里引《中文编码杂谈》中关于乱码的讨论

在Linux平台上如果使用cat等命令查看文件中的中文内容时,可能出现乱码。这也是编码的问题。简单的说是文件时按照A编码保存,但是cat命令按照当前Locale设定的B编码去查看,在B和A不兼容的时候就出现了乱码。

核心概念

十分钟搞清字符集和字符编码

字符集

简单的说字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系

字符编码

字符编码(英语:Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数 –wikipedia

Unicode和UTF-8

Unicode对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。

Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储

而UTF-8就是字符编码,是Unicode规则字库的一种实现形式

Python中的编码问题

Python编码和Unicode一文中描述了python中可能出现的一些编解码难题

原因之一是Python 2.x默认将所有的字符串当做ASCII来对待(python3中会好很多)

当你使用string类型时,实际上会储存一个字节串

解码字节流

你可以把字节流解码(decode)成一个Unicode对象,把一个Unicode 对象编码(encode)为字节流

  • 你最好是尽早的将字节流解码为Unicode(字节流进入程序的时候)
  • 你不能简单地输出一个Unicode对象。它必须在输出前被变成一个字节串

str/unicode

在mac下python2.7.5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# unicode
unicode_a = u"你好"
type(unicode_a) # <type 'unicode'>
unicode_a # u'\u4f60\u597d'
unicode_b = u'\u4f60\u597d' 
unicode_b # u'\u4f60\u597d'
unicode_a == unicode_b # True

# unicode_c = unicode("你好") python2会报错,python会把所有东西作为字节流理解
u'hello' == unicode("hello") #  True,如果

unicode_a.encode("utf-8") # '\xe4\xbd\xa0\xe5\xa5\xbd'
################
# str
str_a = "你好"
str_a # '\xe4\xbd\xa0\xe5\xa5\xbd'
str_a.decode("utf-8") # u'\u4f60\u597d'
str_a.decode("utf-8")  ==  u"你好" # True

repr的目标是准确性,str的目标是可读性

codecs模块

codecs模块能在处理字节流的时候提供很大帮助。你可以用定义的编码来打开文件并且你从文件里读取的内容会被自动转化为Unicode对象。

读写文件

当从一个文件读取数据的时候,codecs.open 会创建一个文件对象能够自动将utf-8编码文件转化为一个Unicode对象,而写入文件这样写:

1
2
3
4
import codecs
fh = codecs.open("/tmp/utf-8.txt", "w", "utf-8")
fh.write(u"\u2013") 
fh.close()

使用urllib流

1
2
3
4
stream = urllib.urlopen("http://www.google.com")
Reader = codecs.getreader("utf-8")
fh = Reader(stream)
type(fh.read(1)) # <type 'unicode'> ,应该尽可能让程序内部的数据都是 <type 'unicode'> 

你必须对codecs模块十分小心。你传进去的东西必须是一个Unicode对象,否则它会自动将字节流作为ASCII进行解码。

策略

python编解码涉及的问题可能很多,上至大神下至小白,都可能受扰,为了保持简单,我们可以保持这样一种习惯:关注输入输出,内部保持unicode。

每当有数据进入程序,将其解码(decode)为unicode(utf-8)

当有数据从程序中输出时,将其编码(encode)为utf-8

最佳实践:

  • 最先解码(解码为unicode对象),最后编码(输出为字节码)
    • 最先解码意味着无论何时有字节流输入,需要尽早将输入解码为Unicode
    • 最后编码意味着只有你打算将文本输出到某个地方时,才把它编码为字节流。这个输出可能是一个文件,一个数据库,一个socket等等
  • 默认使用utf-8编码
  • 使用codecs和Unicode对象来简化处理
    • codecs模块能够让我们在处理诸如文件或socket这样的流的时候能少踩一些坑。如果没有codecs提供的这个工具,你就必须将文件内容读取为字节流,然后将这个字节流解码为Unicode对象。
    • codecs模块能够让你快速的将字节流转化为Unicode对象,省去很多麻烦。

linux下的一些工具

file

file命令用来探测给定文件的类型,

参数:

  • -i:显示MIME类别。
  • -c:详细显示指令执行过程,便于排错或分析程序执行的情形;

file *

iconv

conv命令是用来转换文件的编码方式的,比如它可以将UTF8编码的转换成GB18030的编码

iconv -f encoding -t encoding inputfile

iconv -f UTF-8 -t GBK file1 -o file2 //将一个UTF-8 编码的文件转换成GBK编码

chardet

有时候我们不知道文件/字节流采用了什么编码,可以让chardet来猜测编码,chardet是python的一个库

###传输编码语法(transfer encoding syntax) 用于处理上一层次的字符编码方案提供的字节序列。一般其功能包括两种:一是把字节序列的值映射到一套更受限制的值域内,以满足传输环境的限制,例如Email传输时Base64或者quoted-printable,都是把8位的字节编码为7位长的数据;另一是压缩字节序列的值,如LZW或者进程长度编码等无损压缩技术。

######Base64编码 base64编码相关的部分,可以翻阅我的JWT学习笔记中的附录部分

  • > Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据
  • 常用于在URL、Cookie、网页中传输少量二进制数据。

所谓Base64,就是说选出64个字符—-小写字母a-z、大写字母A-Z、数字0-9、符号”+“、”/“(再加上作为垫字的”=“,实际上是65个字符)—-作为一个基本字符集。然后,其他所有符号都转换成这个字符集中的字符。

关于base64更多的细节可以参考维基百科和Base64笔记

在python中实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import base64
base64.b64encode('hello') #aGVsbG8=  , 被编码的不是应该是二进制数据吧,python的二进制默认被解析为ascii?,在这里hello无论是b'hello'还是'hello'/u'hello',结果都一样
base64.b64decode("aGVsbG8=") # hello

a = u'你好' # \u4f60\u597d
b = base64.b64encode(a.encode("utf-8"))  #5L2g5aW9
c = base64.b64decode(b) # \xe4\xbd\xa0\xe5\xa5\xbd
c.decode("utf-8") # u'\u4f60\u597d'

# 由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_
d = u"哈里谢顿".encode("utf-8") # \xe5\x93\x88\xe9\x87\x8c\xe8\xb0\xa2\xe9\xa1\xbf
base64.b64encode(d) # 5ZOI6YeM6LCi6aG/ , 有反斜杠
e = base64.urlsafe_b64encode(d) # 5ZOI6YeM6LCi6aG_
base64.urlsafe_b64decode(e) # \xe5\x93\x88\xe9\x87\x8c\xe8\xb0\xa2\xe9\xa1\xbf

url中的编码问题

一般而言使用urllib库中的urlencode函数就好了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from urllib import urlencode
data = {
     'name': '小明', #u'小明'则需要encode为字节码
     'age': '10岁'
     }
print urlencode(data) # age=10%E5%B2%81&name=%E5%B0%8F%E6%98%8E

#仅对字符串进行转码可以使用quote
from urllib import quote
quote('小明')  #%E5%B0%8F%E6%98%8E'

如果有大量此类工作可以考虑使用furl

在线工具

一些细碎知识

Windows简体中文版中,ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(繁体中文版会采用Big5码)

python中字节流和unicode的代码视角比较

参考