【5.7.1】Python2中使用中文字符

一、使用中文字符

在python源码中如果使用了中文字符,运行时会有错误,解决的办法是在源码的开头部分加入字符编码的声明

#!/usr/bin/env python
# -*- coding: gb18030 -*- 
或者 # -*- coding: utf-8 -*-

上面的写法是为了美观,当然也可以这么写:

# code: UTF-8   #这个貌似不好用额

我用的是

#coding:UTF-8

常见编码介绍:

  • GB2312编码:适用于汉字处理、汉字通信等系统之间的信息交换
  • GBK编码:是汉字编码标准之一,是在 GB2312-80 标准基础上的内码扩展规范,使用了双字节编码
  • ASCII编码:是对英语字符和二进制之间的关系做的统一规定
  • Unicode编码:这是一种世界上所有字符的编码。当然了它没有规定的存储方式。
  • UTF-8编码:是 Unicode Transformation Format - 8 bit 的缩写, UTF-8 是 Unicode 的一种实现方式。它是可变长的编码方式,可以使用 1~4 个字节表示一个字符,可根据不同的符号而变化字节长度。

Python Tutorial中指出,python的源文件可以编码ASCII以外的字符集,最好的做法是在#!行后面用一个特殊的注释行来定义字符集:

# -*- coding: encoding -*-

根据这个声明,Python会尝试将文件中的字符编码转为encoding编码,并且,它尽可能的将指定地编码直接写成Unicode文本。 注意,coding:encoding只是告诉Python文件使用了encoding格式的编码,但是编辑器可能会以自己的方式存储.py文件,因此最后文件保存的时候还需要编码中选指定的ecoding才行。

二、中文字符的存储

str = u"中文"
 str
u'\xd6\xd0\xce\xc4'
 str = "中文"
 str
'\xd6\xd0\xce\xc4'
u"中文"只是声明unicode,实际的编码并没有变。这样子就发生变化了:
 str = "中文"
 str
'\xd6\xd0\xce\xc4'
 str = str.decode("gb2312")
 str
u'\u4e2d\u6587'
更进一步:
 s = '中文'
 s.decode('gb2312')
u'\u4e2d\u6587'
 len(s)
4
 len(s.decode('gb2312'))
2
 s = u'中文'
 len(s)
4
 s = '中文test'
 len(s)
8
 len(s.decode('gb2312'))
6
 s = '中文test,'
 len(s)
10
 len(s.decode('gb2312'))
7

可以看出,对于实际Non-ASCII编码存储的字符串,python可以正确的识别出其中的中文字符以及中文上下文中的标点符号。 **前缀“u”表示“后面这个字符串“是一个Unicode字符串”,**这仅仅是一个声明,并不表示这个字符串就真的是Unicode了;就好比某正太声称自己已满18岁,但实际上他的真实年龄并不确定,现在体育界年龄造假可不稀罕幺!

那么声明成u有什么作用呢?对于Python来说,只要你声明某字符串是Unicode,它就会用Unicode的一套机制对它进行处理。比方说,做字符串操作的时候会动用到内部的Unicode处理函数,保存的时候以Unicode字符(双字节)进行保存。等等。显而易见,对于一个实际上并不是Unicode的字符串,做Unicode动作的处理,是有可能会出问题的。u前缀只适用于你的字符串常量真的是Unicode的情况。

三、中文字符的IO操作

用python处理字符串很容易,但是在处理中文的时候需要注意一些问题。比如:

 a = "我们是python爱好者"
 print a[0]
只能输出“我”字的前半部分,要想输出整个的“我”字还需要:
 b = a[0:2]
 print b
才行,很不方便,并且当一段文本中同时有中英文如何处理?最好的办法就是转换为unicode。像这样:
 c = unicode(a, "gb2312")
 print c[0]
这个时候c的下标对应的就是每一个字符,不再是字节,并且通过len(c)就可以获得字符数!还可以很方便的转换为其他编码,比如转换为utf-8:
 d = c.encode("utf-8")

但是如果想要在raw_input()时接收中文字符,又会遇到错误。主要是先需要对输入的中文字符进行unicode的解码,然后在进行中文字符集的编码就可以直接传到字符中去,并通过http发到网页上。这里的中文字符集编码可以是gbk,gb2312,gb18030都可以

text = raw_input( "Input the contents: " )
text = unicode( text, 'gbk' ).encode( 'gb18030' )

编码转换:

Python内部的字符串一般都是 Unicode编码。代码中字符串的默认编码与代码文件本身的编码是一致的。所以要做一些编码转换通常是要以Unicode作为中间编码进行转换的,即先将其他编码的字符串解码(decode)成 Unicode,再从 Unicode编码(encode)成另一种编码。

decode 的作用是将其他编码的字符串转换成 Unicode 编码,eg name.decode(“GB2312”),表示将GB2312编码的字符串name转换成Unicode编码

encode 的作用是将Unicode编码转换成其他编码的字符串,eg name.encode(”GB2312“),表示将GB2312编码的字符串name转换成GB2312编码 所以在进行编码转换的时候必须先知道 name 是那种编码,然后 decode 成 Unicode 编码,最后载encode 成需要编码的编码。当然了,如果 name 已经就是 Unicode 编码了,那么就不需要进行decode 进行解码转换了,直接用 encode 就可以编码成你所需要的编码。值得注意的是:对 Unicode 进行编码和对 str 进行编码都是错误的。

字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。 <type ‘str’> 将字符串看作是字节的序列,而 <type ‘unicode >’则将其看作是字符的序列,单个字符可能占用多个字节;字节相对于字符,其在存储层次中更低一些。 str转换为unicode要decode,可以这样想,因为要把字节序列解释成字符序列,字节序列是底层的存放方式,解码(decode)成更高层的字符以便使用;同理,unicode转换为str要encode,就象信息编码(encode)后才存储一样:

处理中文数据时最好采用如下方式:

  1. Decode early(尽早decode, 将文件中的内容转化成unicode再进行下一步处理)
  2. Unicode everywhere (程序内部处理都用unicode)
  3. Encode late (最后encode回所需的encoding, 例如把最终结果写进结果文件)

下面是一个简单的演示,用re库查询一个中文字符串并打印:

p = re.compile(unicode("测试(.*)", "gb2312"))
s = unicode("测试一二三", "gb2312")
for i in p.findall(s):
print i.encode("gb2312")
一二三

四、python写文件

由于内置函数 open() 打开文件时,read() 读取的是 str,读取后需要使用正确的编码格式进行 decode()。write() 写入时,如果参数是 Unicode,则需要使用你希望写入的编码进行 encode(),如果是其他编码格式的 str,则需要先用该 str 的编码进行 decode(),转成 Unicode 后再使用写入的编码进行 encode()。如果直接将 Unicode 作为参数传入 write() ,python 将先使用源代码文件声明的字符编码进行编码然后写入。

	# coding: UTF-8
	 
	fp1 = open('test.txt', 'r')
	info1 = fp1.read()
	# 已知是 GBK 编码,解码成 Unicode
	tmp = info1.decode('GBK')
	fp2 = open('test.txt', 'w')
	# 编码成 UTF-8 编码的 str
	info2 = tmp.encode('UTF-8')
	fp2.write(info2)
	fp2.close()
	
	获取编码的方式:
	判断是 s 字符串否为Unicode,如果是返回True,不是返回False :
	isinstance(s, unicode)
	
	下面代码可以获取系统默认编码:
	#!/usr/bin/env python
	#coding=utf-8
	import sys
	print sys.getdefaultencoding()

我的方法:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

从文本读进来的汉字必须decode一下
status =seqs_status_info[seq_name]['status'].decode('utf-8')

五、删掉unicode 字符串前面的 u

有时我们会碰到类似下面这样的 unicode 字符串:

u'\xe4\xbd\xa0\xe5\xa5\xbd'

那如何才能得到我们想要的 \xe4\xbd\xa0\xe5\xa5\xbd 呢? python 提供了一个特殊的编码( raw_unicode_escape )用来处理这种情况:

In [4]: u'\xe4\xbd\xa0\xe5\xa5\xbd'.encode('raw_unicode_escape')
Out[4]: '\xe4\xbd\xa0\xe5\xa5\xbd'

In [5]: u'\xe4\xbd\xa0\xe5\xa5\xbd'.encode('raw_unicode_escape').decode('utf8')
Out[5]: u'\u4f60\u597d'

In [7]: print u'\u4f60\u597d'
你好

六、小结

一个比较一般的Python中文处理的流程:

  • 将欲处理的字符串用unicode函数以正确的编码转换为Unicode
  • 在程序中统一用Unicode字符串进行操作
  • 输出时,使用encode方法,将Unicode再转换为所需的编码

有几点要说明一下:

  • 所谓“正确的”编码,指得是指定编码和字符串本身的编码必须一致。这个其实并不那么容易判断,一般来说,我们直接输入的简体中文字符,有两种可能的编码:GB2312(GBK、GB18030)、以及UTF-8
  • encode成本地编码的时候,必须要保证目标编码中存在欲转换字符的内码。encode这种操作一般是通过一个本地编码对应Unicode的编码转换表来进行的,事实上每个本地编码只能映射到Unicode的一部分。但是映射的区域是不同的,比如Big-5对应的Unicode的编码范围和 GBK对应的就不一样(实际上这两个编码有部分范围是重叠的)。所以,Unicode的一些字符(比如本身就是从GB2312转换来的那些),可以映射到 GBK,但未必可以映射到Big-5,如果你想转换到Big-5,很有可能就会出现编码找不到的异常。但UTF-8的码表范围实际上和Unicode是一样的(只是编码形式不同而已),所以,理论上来说,任何本地编码的字符,都可以被转换到UTF-8
  • GB2312、GBK、GB18030本质上是同一种编码标准。只是在前者的基础上扩充了字符数量
  • UTF-8和GB编码不兼容

七、报错

  1. UnicodeEncodeError: ‘ascii’ codec can’t encode character u'\u3000' in position 0: ordinal not in range(128)

解决办法:

#coding:UTF-8
tmplt = "{0:^10}\t{1:{3}^10}\t{2:^10}" 
print tmplt.format('排名',"学校名称","总分",unichr(12288).encode('utf-8'))

报错2

在python2.7下,因为想从数据库中读出来分类名进行写入到文件,提示

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    fp.write("%d:%s\r\n"%(sClassid,sClassName))
UnicodeEncodeError: 'ascii' codec can't encode character u'\uff08' in position 12: ordinal not in range(128

不用fp.write,用print打印却正常,这到底是怎么回来呢?

#! /usr/bin/python
# -*- coding: utf-8 -*-
import sys
print sys.getdefaultencoding();

运行上面的程序提示

ascii

解决办法:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

再次运行,错误消息。

总结一下,python2.7是基于ascii去处理字符流,当字符流不属于ascii范围内,就会抛出异常(ordinal not in range(128))。

参考资料

药企,独角兽,苏州。团队长期招人,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn