笔记汇总———Python 处理中文的一些小技巧UNICODE encoding

python对多国语言的处理是支持的很好的,它可以 处理现在任意编码的字符,这里深入的研究一下python对多种不同语言的处理。

首先要理解字符集, 和源代码存储编码

关于Python处理Unicode,我所能找到的最言简意赅的入门教程是:

Unicode In Python, Completely Demystified (揭秘 Python Unicode)

 

File system filename encodings for Linux and Windows

 

常用字符集分类

GBK向下与GB2312 完全兼容,向上支持ISO 10646国际标准

GB18030字符集 作用:它解决了中文、日文、朝鲜语等的编码,兼容GBK。

 

ASCII及其扩展字符集

作用:表语英语及西欧语言。

位数:ASCII是用7位表示的,能表示128个字符;其扩展使用8位表示,表示256个字符。

范围:ASCII从00到7F,扩展从00到FF。

ISO-8859-1字符集

作用:扩展ASCII,表示西欧、希腊语等。

位数:8位,

范围:从00到FF,兼容ASCII字符集。

GB2312字符集

作用:国家简体中文字符集,兼容ASCII。

位数:使用2个字节表示,能表示7445个符号,包括6763个汉字,几乎覆盖所有高频率汉字。

范围:高字节从A1到F7, 低字节从A1到FE。将高字节和低字节分别加上0XA0即可得到编码。

BIG5字符集

作用:统一繁体字编码。

位数:使用2个字节表示,表示13053个汉字。

范围:高字节从A1到F9,低字节从40到7E,A1到FE。

GBK字符集

作用:它是GB2312的扩展,加入对繁体字的支持,兼容GB2312。

位数:使用2个字节表示,可表示21886个字符。

范围:高字节从81到FE,低字节从40到FE。

GB18030字符集

作用:它解决了中文、日文、朝鲜语等的编码,兼容GBK。

位数:它采用变字节表示(1 ASCII,2,4字节)。可表示27484个文字。

范围:1字节从00到7F; 2字节高字节从81到FE,低字节从40到7E和80到FE;4字节第一三字节从81到FE,第二四字节从30到39。

UCS字符集

作用:国际标准 ISO 10646 定义了通用字符集 (Universal Character Set)。它是与UNICODE同类的组织,UCS-2和UNICODE兼容。

位数:它有UCS-2和UCS-4两种格式,分别是2字节和4字节。

范围:目前,UCS-4只是在UCS-2前面加了0×0000。

UNICODE字符集

作用:为世界650种语言进行统一编码,兼容ISO-8859-1。

位数:UNICODE字符集有多个编码方式,分别是UTF-8,UTF-16和UTF-32。

 

源文件编码

Python will default to ASCII as standard encoding if no other

encoding hints are given.

To define a source code encoding, a magic comment must

be placed into the source files either as first or second

line in the file, such as:

 

# coding=<encoding name>

 

or (using formats recognized by popular editors)

 

#!/usr/bin/python

# -*- coding: <encoding name> -*-

 

or

 

#!/usr/bin/python

# vim: set fileencoding=<encoding name> :

 

Python 处理中文的时候的一些小技巧

 

相信第一次处理中文的朋友们可能都会对中文的encoding 和程序的报错很头疼。

如果你像我一样希望能够把事情尽快做好而不去深究,你可能会写一些异常处理的代码把 UnicodeEncodingError糊弄过去先,但当你开始怀疑有多少encoding出错的信息被你丢弃的时候,可能你会很惊奇。于是,你还是会 想坐下来,(洗把脸)然后面对自己必须弄懂什么是utf-8,什么是 ‘gb2312′, 什么是 ‘gbk’ 和其中的猫腻。正如有时候猛撕小伤口上邦迪胶布的快感一样,有时候当你认真面对一些你平时一直回避的问题的时候(其实有时候需要的不是勇气), 你反而会觉得“不过如此”,并且能够一劳永逸的解决问题。

简要罗列一下最重要最实用的点:

Solution

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

python的print和 write 对编码的不同处理

python的print会对输出的文本做自动的编码 转换,而文件对象的write方法就不会做,因此,当一些字符串用print输出正常时,write到文件确不一定和print的一样。
print转换的目的编码和环境变量有关,Windows XP是转换为gbk的。在linux下是按照环境变量来转换的。在linux下使用locale命令就可以看到。比如我的是:

 

Correctly detecting the encoding all times is impossible.

(From chardet FAQ:)

However, some encodings are optimized for specific languages, and languages are not random. Some character sequences pop up all the time, while other sequences make no sense. A person fluent in English who opens a newspaper and finds “txzqJv 2!dasd0a QqdKjvz” will instantly recognize that that isn’t English (even though it is composed entirely of English letters). By studying lots of “typical” text, a computer algorithm can simulate this kind of fluency and make an educated guess about a text’s language.

There is the chardet library that uses that study to try to detect encoding. chardet is a port of the auto-detection code in Mozilla.

You can also use UnicodeDammit. It will try the following methods:

  • An encoding discovered in the document itself: for instance, in an XML declaration or (for HTML documents) an http-equiv META tag. If Beautiful Soup finds this kind of encoding within the document, it parses the document again from the beginning and gives the new encoding a try. The only exception is if you explicitly specified an encoding, and that encoding actually worked: then it will ignore any encoding it finds in the document.
  • An encoding sniffed by looking at the first few bytes of the file. If an encoding is detected at this stage, it will be one of the UTF-* encodings, EBCDIC, or ASCII.
  • An encoding sniffed by the chardet library, if you have it installed.
  • UTF-8
  • Windows-1252

 

字符串操作

>>> s = u’Was ever feather so lightly blown to and fro as this multitude?’

>>> s.count(‘e’)  # default ascii

5

>>> s.find(‘feather’)

9

>>> s.find(‘bird’)

-1

>>> s.replace(‘feather’, ‘sand’)

u’Was ever sand so lightly blown to and fro as this multitude?’

>>> s.upper()

u’WAS EVER FEATHER SO LIGHTLY BLOWN TO AND FRO AS THIS MULTITUDE?’

Note that the arguments to these methods can be Unicode strings or 8-bit strings. 8-bit strings will be converted to Unicode before carrying out the operation; Python’s default ASCII encoding will be used, so characters greater than 127 will cause an exception:

>>>

>>> s.find(‘Was\x9f’)

Traceback (most recent call last):

UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0x9f in position 3:

ordinal not in range(128)

>>> s.find(u’Was\x9f’)

-1

 

Windows default to GBK, while linux depends on Locale

[zhaowei@papaya zhaowei]$ locale
LANG=zh_CN
LC_CTYPE=”zh_CN”
LC_NUMERIC=”zh_CN”
LC_TIME=”zh_CN”
LC_COLLATE=”zh_CN”
LC_MONETARY=”zh_CN”
LC_MESSAGES=”zh_CN”
LC_PAPER=”zh_CN”
LC_NAME=”zh_CN”
LC_ADDRESS=”zh_CN”
LC_TELEPHONE=”zh_CN”
LC_MEASUREMENT=”zh_CN”
LC_IDENTIFICATION=”zh_CN”
LC_ALL=

这个时候会认为是gb2312的。在python中可以用locale模块来获得当前环境的编码:

1.  import locale

  1. print locale.getdefaultlocale()

 

print在输出时把字符串自动装换为这个编码。看看 下面,”喆”这个字是很著名的一个在gb2312中没有的字,当把它转换为gb2312的时候是会出错的。

 

1.  #-*- encoding: gb18030 -*-

  1. import locale
  2. import sys, encodings, encodings.aliases
  3. # 现在a是unicode的
  4. a = u’喆’
  5. print a.encode(“gb2312”)

 

上面这段代码会报异常,就是这个原因。但如果是直接 print a 就可以输出来 (假设你的环境变量是GBK或者GB18030或者UTF-8)。如果你的环境变量是GB2312的,那这个print一样会报错!所以在处理其他地方来 的文本数据时,最好不要用GB2312的编码,是中文数据,一定要用GB18030或者UTF-8!     而用文件对象的write写unicode的数据也是会出错的!需要做编码转换。

 

1.  #-*- encoding: gb18030 -*-

  1. import locale
  2. import sys, encodings, encodings.aliases
  3. # 现在a是unicode的
  4. a = u’喆’
  5. f = open(“aaa.txt”, “w”)
  6. f.write(a)
  7. f.close()

1. Decode early

Decode to <type ‘unicode’> ASAP
>>> def to_unicode_or_bust(
…         obj, encoding=’utf-8′):
…     if isinstance(obj, basestring):
…         if not isinstance(obj, unicode):
…             obj = unicode(obj, encoding)
…     return obj

>>>
detects if object is a string and if so converts to unicode, if not already.

2. Unicode everywhere

>>> to_unicode_or_bust(ivan_uni)
u’Ivan Krstiu0107′
>>> to_unicode_or_bust(ivan_utf8)
u’Ivan Krstiu0107′
>>> to_unicode_or_bust(1234)
1234

3. Encode late

Encode to <type ‘str’> when you write to disk or print
>>> f = open(‘/tmp/ivan_out.txt’,’w’)
>>> f.write(ivan_uni.encode(‘utf-8′))
>>> f.close()

我以前一直觉得unicode相关的处理都是很 dirty 的工作,一般都会一边尝试,一边用异常处理去补丁,看完以上这个教程以后豁然开朗。

祝大家也能早日理清处理中文的时候的头绪,坦然直面“神秘”的unicode

 

C++ libiconv以前我写了一个gb18030到utf-8编码转换的 程序,这段代码还是有些问题的,因为现在我需要对任意的两个iconv支持的语言编码做互相转换,比如GB2312, GBK, GB18030, UTF-8, UTF-16, BIG5等等,所以才有了这段程序,注释我不加了。目前这段代码是非常的稳定,测试了超过10万行的数十种编码的文本的转换都没有出问题。

有一点需要清楚的是,当python要做编码转换的时 候,会借助于内部的编码,转换过程是这样的:

原有编码 -> 内部编码 -> 目的编码

python的内部是使用unicode来处理的,但是unicode的使用需要考虑的是它的编码格式有两种,一是UCS-2,它一共有65536个码 位,另一种是UCS-4,它有2147483648g个码位。对于这两种格式,python都是支持的,这个是在编译时通过–enable- unicode=ucs2或–enable-unicode=ucs4来指定的。那么我们自己默认安装的python有的什么编码怎么来确定呢?有一个 办法,就是通过sys.maxunicode的值来判断:

import sys
print sys.maxunicode

如果输出的值为65535,那么就是UCS-2,如果输出是1114111就是UCS-4编码。
我们要认识到一点:当一个字符串转换为内部编码后,它就不是str类型了!它是unicode类型:

a = “风卷残云”
print type(a)
b = a.unicode(a, “gb2312”)
print type(b)

输出:

 

<type ‘str’>
<type ‘unicode’>

这个时候b可以方便的任意转换为其他编码,比如转换为utf-8:

c = b.encode(“utf-8”)
print c

c输出的东西看起来是乱码,那就对了,因为是utf-8的字符串。
好了,该说说codecs模块了,它和我上面说的概念是密切相关的。codecs专门用作编码转换,当然,其实通过它的接口是可以扩展到其他关于代码方面 的转换的,这个东西这里不涉及。

1.  #-*- encoding: gb2312 -*-

  1. import codecs, sys
  2. print ‘-‘*60
  3. # 创建gb2312编码器
  4. look = codecs.lookup(“gb2312”)
  5. # 创建utf-8编码器
  6. look2 = codecs.lookup(“utf-8”)
  7. a = “我爱北京天安门”
  8. print len(a), a
  9. # 把a编码为内部的unicode, 但为什么方法名为decode呢,我的理解是把gb2312的字符串解码为unicode
  10. b = look.decode(a)
  11. # 返回的b[0]是数据,b[1]是长度,这个时候的类型是unicode了
  12. print b[1], b[0], type(b[0])
  13. # 把内部编码的unicode转换为gb2312编码的字符串,encode方法会返回一个字符串类型
  14. b2 = look.encode(b[0])
  15. # 发现不一样的地方了吧?转换回来之后,字符串长度由14变为了7! 现在的返回的长度才是真正的字数,原来的是字节数
  16. print b2[1], b2[0], type(b2[0])
  17. # 虽然上面返回了字数,但并不意味着用len求b2[0]的长度就是7了,仍然还是14,仅仅是codecs.encode会统计字数
  18. print len(b2[0])

 

上面的代码就是codecs的使用,是最常见的用法。另外还有一个问题就是,如果我们处理的文件里的字符编码是其他类型的呢?这个读取进行做处理也需要特 殊的处理的。codecs也提供了方法.

#-*- encoding: gb2312 -*- import codecs, sys

# 用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode

 

bfile = codecs.open(“dddd.txt”, ‘r’, “big5”) #bfile = open(“dddd.txt”, ‘r’) ss = bfile.read() bfile.close()

 

# 输出,这个时候看到的就是转换后的结果。如果使用语言内建的open函数来打开文件,这里看到的必定是乱码

 

print ss, type(ss)

上面这个处理big5的,可以去找段big5编码的文件试试

Python Unicode 字符串的理解

coding:gb2312

Python代码

  1. # -*- coding:gb2312 -*-
  2. if __name__==’__main__’:
  3. print “————-code 1—————-“
  4. a = “和谐b你b可爱女人”
  5. print a
  6. print a.find(“你”)   #index=5,对于一般字符串,按照了
  7. #指定的编码方式(这里为gb2312)
  8. #并不像unicode字符串一样,把任何字符视为长度1,
  9. #而是视为字节长度(5=2+2+1).
  10. b = a.replace(“爱”, “喜欢”)
  11. print b
  12. print “————–code 2—————-“
  13. x = “和谐b你b可爱女人”
  14. print a.find(“你”)
  15. y = unicode(x) #此处将x解码(成字符串),如果有编码第二参数,应该和第一行指示编码相同
  16. print y
  17. print y.encode(“utf-8”) #若和指示编码不一样,则会打印乱码
  18. print y.encode(“gb2312”)
  19. print y.find(u”你”)  #index=3,因为unicode字符都视为1长度
  20. z = y.replace(u”爱”, u”喜欢小”)
  21. print z.encode(“utf-8”)
  22. print z.encode(“gb2312”)
  23. print “—————code 3—————-“
  24. print y
  25. newy = unicode(x,”gb2312″) #如果和指示编码行的指示不一样的话,将报错
  26. print newy

输出:

引用
————-code 1—————-
和谐b你b可爱女人
5
和谐b你b可喜欢女人
————–code 2—————-
5
和谐b你b可爱女人
???b浣????濂充??
和谐b你b可爱女人
3
???b浣????娆㈠?濂充??
和谐b你b可喜欢小女人
—————code 3—————-
和谐b你b可爱女人
和谐b你b可爱女人

utf-8版本的编码指示行:

Python代码

  1. # -*- coding:utf-8 -*-
  2. if __name__==’__main__’:
  3. print “————-code 1—————-“
  4. a = “和谐b你b可爱女人”
  5. print a
  6. print a.find(“你”)   #index=7,对于一般字符串,按照了指定的编码方式(这里为utf-8)
  7. #并不像unicode字符串一样,把任何字符视为长度1,
  8. #而是视为字节长度(7=3+3+1).
  9. b = a.replace(“爱”, “喜欢”)
  10. print b
  11. print “————–code 2—————-“
  12. x = “和谐b你b可爱女人”
  13. print a.find(“你”)#同—-code 1—-,index=7
  14. y = unicode(x) #此处将x解码(成字符串),如果有编码第二参数,应该和第一行指示编码相同
  15. print “直接print::”,y
  16. print “若和指示编码不一样,以下两行有一行会打印乱码”
  17. print “UTF-8::”,y.encode(“utf-8”)
  18. print “GB2312::”,y.encode(“gb2312”)
  19. print y.find(u”你”)  #index=3,因为unicode字符都视为1长度
  20. z = y.replace(u”爱”, u”喜欢小”)
  21. print “若和指示编码不一样,以下两行有一行会打印乱码”
  22. print z.encode(“utf-8”)
  23. print z.encode(“gb2312”)
  24. print “—————code 3—————-“
  25. print “直接print::”,y
  26. newy = unicode(x,”gb2312″) #如果和指示编码行的指示不一样的话,将报错
  27. print newy

输出:

引用

————-code 1—————-
和谐b你b可爱女人
7
和谐b你b可喜欢女人
————–code 2—————-
7
直接print:: 和谐b你b可爱女人
—–若和指示编码不一样,以下两行有一行会打印乱码—-
UTF-8:: 和谐b你b可爱女人
GB2312:: ��гb��b�ɰ�Ů��
3
—-若和指示编码不一样,以下两行有一行会打印乱码—–
和谐b你b可喜欢小女人
��гb��b��ϲ��СŮ��
—————code 3—————-
直接print:: 和谐b你b可爱女人
Traceback (most recent call last):
File “E:\JavaWork\WorkForLab\PythonStarter\src\LangProbe\__init__.py”, line 28, in <module>
newy = unicode(x,”gb2312″) #如果和指示编码行的指示不一样的话,将报错
UnicodeDecodeError: ‘gb2312’ codec can’t decode bytes in position 0-1: illegal multibyte sequence

我的解释……还请各位指出谬误.

笔者使用的文本物理编码始终是utf-8,但是对指示行编码进行了更改操作.看起来:
虽然,如果在eclipse编辑器中改掉第一行的编码指定为一个不兼容的编码,比如”utf-8″->”gbk”,那么保存后整个文件就会 出乱码.但是这是eclipse的python编辑器的特性,如果再将事先预存的无乱码的内容覆盖拷回代码中,程序仍将正常显示和运行,而且查看文件发现 物理编码并无改变.因此,文本的物理编码和python指示行的编码实际上并不干扰,是两回事.
(有趣的是,在eclipse中改变了一次指示行编码保存后(utf8到gb2312),代码显示为乱码,如果再改回,则eclipse报错,因 为按乱码显示的文本无法再保存,编码已被破坏;如果再ctrl-z回退,使得指示行再为utf-8保存,文件显示为之前不是乱码的状态,保 存,eclipse不报错,但是查看此时物理编码变成了ANSI)

硬盘阶段:
*.py文件编码是utf-8,然后eclipse内置文本编辑器能够正确地以此编码读入内存,交给python解释器程序(本示例中文件编码是utf-8).
内存中python解释器阶段:
python解释器看到第一二行有编码指示行,则把程序代码编码成该指示编码.(本示例1中,就是gb2312;示例2中是utf-8).
内存中运行时阶段
运行时遇到一般字符串,则按照解释器的编码读入,print直接把此编码字节推送OS打印.若遇到unicode字符串,无编码参数创建的,则按照 程序开头第一行的指示编码创建;若有编码参数则此参数必须和第一行指示编码参数相同或者兼容,否则程序报错,因为python会直接把一般字符串的字节 (已为指示行编码)强行解码为语句指定编码来构造unicode字符,一般会失败.相同了之后,print这个unicode字串,python将以系统 (系统语义:到底是那个级别的系统)默认编码来解码显示,如果系统默认编码不支持unicode字符,比如ascii,那么显然会出错.

 

 

This entry was posted in Programming. Bookmark the permalink.