Python Unicode

Python Unicode

Jan 14, 2014
Coding
Python, 字符编码

本文内容适用于 python 2.x 版本

Python源代码的编码 #

当在源码中使用了任何中文字符,包括变量中或注释中存在的中文字符,需要指定源码的字符集,如果不指定默认为ascii编码。如:

var = 'abc' #这是注释
print var

执行时会报错

SyntaxError: Non-ASCII character ‘\xd5’ in file test.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

这是因为源码中存在中文字符(即便是在注释中)。解决的方法是在源码的开头指定源码本身的编码。通常的写法有:

#coding=utf-8

或者

# -*- coding:utf-8 –*-

指定编码为utf-8。

参考 PEP 0263 –Defining Python Source Code Encodings

str类型和unicode类型字符串 #

Python中有两种类型的字符串,普通字符串str和unicode字符串。unicode字符串使用u前缀来标记,如u’abc’。

str类型是8位字符串(8-bit string),实际上是字节串,str中每个字符,长度为1,实际上就是一个字节。str类型字符串的编码与源码的相同。比如:

#coding=utf-8
var = '中文abc'
var1 = unicode(var,'utf-8')#用utf-8解码var,成功;换成gbk则失败,因为var为utf-8编码
print var1

指定源码的编码为coding=utf-8,则str类型的var会被编码为utf-8。第三行中我们用utf-8编码去解码var可以成功,如果换成gbk,则会失败,但如果指定源码类型为coding=gbk,则可以用gbk解码,utf-8则会失败。这个结果也验证了“str类型字符串的编码与源码的相同”这个结论。[1]

unicode类型字符串可以认为是没有编码的,它用unicode可以表示所有的字符,但是unicode本身只是一种表示码,比如”汉”字的Unicode编码是6C49,\u6C49表示”汉”,但是它没有规定编码的存储和传输的格式(如\u6C49 是存储成大端模式6C 49,还是小端模式49 6C),所以可以编码成其它的格式,如utf-8、utf-16、gbk等等(理论上unicode可以转成其它任何编码)。[2]

关于str和unicode总结如下:

str类型是有编码的(存储、传输编码)的,str的编码和源码的编码相同,如coding=utf-8指定为utf-8,没有指定则默认为ascii

unicode类型可认为是无编码的。unicode是一个字符的名字,看到这个unicode就知道这个字符是什么(如看到\u6C49就知道这是’汉’),但是知道了是什么后具体如何保存和传输这个字符就需要使用另一个存储码了,比如使用utf-8去存储。所以,保存一个unicode类型字符串到磁盘时,必须再编码(encode)成一个其它的存储编码(如ascii、utf-8、utf-16、gbk、gb2312等等)

编码转换相关的函数主要有encode、decode和unicode()。

decode 和 encode #

在Python内部编码为Unicode,编解码时通常需要以unicode作为中间编码。上节提过在unicode是可以认为是无编码。所以

encode,将unicode字符串编码为其它编码的字符串。

decode,将其它编码的字符串解码为unicode字符串。

#coding=utf-8

#encode
var = u'中文abc'
var_utf8 = var.encode('utf-8')#要编码得到utf8字符串
var_gbk = var.encode('gbk')
var_gb2312 = var.encode('gb2312')
print type(var_utf8),type(var_gbk),type(var_gb2312)
#输出 <type 'str'> <type 'str'> <type 'str'>

#decode
var_utf8_d = var_utf8.decode('utf-8')#将utf8字符串解码成unicode
var_gbk_d = var_gbk.decode('gbk')
var_gb2312_d = var_gb2312.decode('gb2312')
print type(var_utf8_d),type(var_gbk_d),type(var_gb2312_d)
#输出:<type 'unicode'> <type 'unicode'> <type 'unicode'>

encode时一定知道要encode的字符串原来是什么编码,decode时要指明将unicode转为其它什么编码。

如果要将一个非unicode编码的字符串转换成另一个非unicode编码的字符串,需要借助unicode,即现将原字符串解码成unicode,再将unicode编码为目标编码字符串。如将utf-8编码的字符串var_utf8转换为gbk编码的var_gbk,代码如下:

var_gbk = var_utf8.decode('utf-8').encode('gbk')

注意:decode一个unicode字符串或encode一个str类型字符串都是不对的。因为前者已经是无编码状态,无需再decode,后者已经是有编码状态,不能在直接编码。 有一种情况是,如果str类型是可以用ascii解码的,则可直接encode成另一个类型,因为会使用默认的ascii编码器对str先解码,然后再编码。如下代码:

var = "this is string example….wow!!!"
var1 = var.decode().encode('base64')
var2 = var.encode('base64');
print var1 #输出:dGhpcyBpcyBzdHJpbmcgZXhhbXBsZS4uLi53b3chISE=
print var2 #输出:dGhpcyBpcyBzdHJpbmcgZXhhbXBsZS4uLi53b3chISE=[/python]

var为不含非ASCII字符,可以使用ascii编码解码,所以,可以直接再编码成base64编码格式,这和先解码成unicode,再将unicode编码成base64是相同的。

推荐方式:编码转换时,利用unicode作为中介转换。

unicode()函数 #

函数原型:

unicode(string[, encoding, errors])

表示将encoding编码类型的string,转换为unicode编码。和str.decode(encoding)作用相同。

不指定encoding情况下,如果string本身为unicode类型,则unicode()函数不做处理,直接返回,如果string不是unicode类型,而是str类型,则利用默认的ascii编码器进行转换,如果string全为ascii字符,则可以转换成功,如果发现存在非ascii字符,则会出错。

代码1: 含非ASCII字符,默认解码,出错。

# -*- coding:utf-8 -*-
var = '中文abc'
var1 = unicode(var)
print var1
#UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)[/python]

var为str类型,并且含有非ascii码字符,所以unicode(var)执行时调用默认的ascii编码器会出错,因为存在它不认识的字符’中’和’文’

代码2:不含非ASCII字符,默认解码,正确。

# -*- coding:utf-8 -*-
var = 'abc' #<type 'str'>
var1 = unicode(var)
print var1,type(var1)#输出:abc <type 'unicode'>

var2 = u'中文abc'
var3 = unicode(var)
print var3,type(var3)#输出:abc <type 'unicode'>[/python]

var为str类型,但是不含非ascii字符,所以unicode(var)使用默认编码器ascii解码后能够成功得到unicode类型的var1。

代码3:utf-8 解码成 unicode

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

var = u'中文abc'
var_utf8 = var.encode('utf-8')#要编码得到utf8字符串

uvar = unicode(var_utf8,'utf-8')
print uvar,type(uvar)#中文abc <type 'unicode'>

var_utf8为utf-8编码的字符串,通过unicode()函数解码成unicode类型字符串。

Unicode使用技巧 #

为了编码程序中产生编码相关的异常和错误,正确的使用unicode,应该遵循一个规则:[4]

总在IO动作的关口做转换。

  1. 无论何时,当你的程序接受到了来自“外部”的文本数据(来自网络、文件、或者用户输入等)时,应当立即创建一个unicode对象,找出最合适的编码,如查看HTTP头,或者寻找一个合适的转换方法来确定所用的编码方式。

  2. 无论何时,当你的程序需要向’外部’发送文本数据(发到网络、写入文件、或者输出给用户等)时,应当探察正确的编码,并用那种编码将你的文本转化成字节串(即str类型有特定编码的字符串)。(否则,Python会尝试把Unicode转成ASCII字节串,这很有可能发送UnicodeEncodeError异常)

Unicode处理文件读写 #

  1. 直接保存含有中文的str类型字符串到文件中,文件编码和源码的编码相同,不含BOM。
# -*- coding:utf-8 -*-

content = "猪之草房子"

f = file('example.txt','w')
f.write(content)
f.close()

将str类型的含有中文的content字符串,直接保存到文件example.txt中。则exmaple.txt文件的编码和Python源码的编码相同,即此处为utf-8。

验证方法:使用windows记事本新建文本文档(参阅:Windows文本文件编码(ANSI和Unicode)),输入“猪之草房子”几个字,然后另存为UTF-8格式的文件utf8.txt。使用UltroEdit打开utf8.txt和example.txt,并显示十六进制,如下所示

utf8 example

utf8.txt开头三个字节EE BB BF为BOM,表示这是utf8格式文档,后面为文档内容。example.txt不含BOM,文本十六进制编码和utf8.txt相同,所以也是utf8格式。

如果content中不含中文,全为ASCII字符,则保存的文件为ASCII码格式。

  1. 不能直接保存含非ASCII字符的unicode类型字符串到文件中

原因前文已经讲了,因为unicode只是字符的表示码,不是实际的存储编码。

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

content = u"猪之草房子"

f = file('example1.txt','w')
f.write(content) #错误
f.close()
#UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128)[/python]

如果content 不含非ASCII字符,如content= ‘JarvisChu’,则上述代码正确。因为存在一个默认的ASCII编码过程。

  1. 保存字符串到文件时指定编码类型
# -*- coding:utf-8 -*-

content = "中文abc"

f = file('example.txt','w')
f.write(content.decode('utf-8').encode('gbk'))
f.close()

将utf-8编码的字符串content转换成gbk格式编码保存。则example.txt为gbk编码。十六进制打开example.txt,可见使用的是gbk编码,一个汉字占两个字节,一个字母占一个字节。

gbk

  1. 读取文件
# -*- coding:utf-8 -*-

f = file('example.txt')
content = f.read()
f.close()
print content

f.read()会使用源码文件的编码utf-8来读取文件example.txt。所以content的编码为utf-8,但是如果example的编码不是utf-8,那么content就是乱码。

遵循上节的原则,最好的方法文件读取方法是:

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

f = file('example.txt')
content = f.read().decode('utf-8')
f.close()
print content,type(content)#中文abc <type 'unicode'>[/python]

即在读取时,根据文件的编码(比如文件编码为utf-8)解码成unicode,之后处理unicode。就可以避免乱码问题。

注:简体中文Windows系统中,使用记事本创建保存的文本文件,默认的编码是gbk,则可以使用gbk去解码。

参考资料 #

[1]. python的str,unicode对象的encode和decode方法(转)

[2]. http://farmdev.com/talks/unicode/

[3]. Python Unicode 文档

[4]. Python Cookbook – 1.20节 – 使用Unicode来处理国际化文本

[5]. Windows文本文件编码(ANSI和Unicode)