上篇字符编码对字符编码做了简单的介绍,除了演示几个简单的例子外,没有将字符编码编程语言,看本文的标题相信就明白,这是与伟大的python结合一些实例(python版本为2.7.5)。

准备工作,去码表(GB2312, Unicode, UTF-8)查取编码的GB2312和Unicode字符集编码及按gb2312和utf-8编码方式保存的二进制。注意,要亲自查一下 哦~~

编码                  对应十六进制
GB2312字符集:        b1e0 c2eb
GB2312编码(同上):     b1e0 c2eb
Unicode字符集:       7f16 7801
UTF-16编码(同上):     7f16 7801
UTF-8编码:            e7bc96 e7a081

字符串操作

话不多说,打开python解释器,输入:

>>> s = '编码'
>>> s
'\xb1\xe0\xc2\xeb'

将结果与码表查得的结果,对应着GB2312字符集。为什么呢?继续:

>>> import sys
>>> sys.getdefaultencoding() #返回当前系统所使用的默认字符编码
'ascii'

除一些IDE外,python是按操作系统默认字符编码来处理的。这里是ascii。

python支持unicode:

>>> u = u'编码' #unicode编码
>>> u
u'\u7f16\u7801'

utf-8:

>>> u = u'编码'
>>> u
u'\u7f16\u7801'
>>> type(u)
<type 'unicode'>
>>> s = u.encode('utf-8')
>>> s
'\xe7\xbc\x96\xe7\xa0\x81'
>>> type(s)
<type 'str'>
>>> u1 = s.decode('utf-8')
>>> u1
u'\u7f16\u7801'

综上,可与查得的结果相同。

python中的str,unicode对象:

  • str: str对象其实就是"8-bit string" ,字节字符串,本质上类似java中的byte[]。decode
  • unicode: unicode对象应该才是等同于java中的String对象,或本质上是java的char[]。encode

unicode才是真正意义上的字符串:

>>> s = '编码'
>>> len(s)
4
>>> u = u'编码'
>>> len(u)
2

详细请参见python的str,unicode对象的encode和decode方法

文件操作

读文件

内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。

如保存为ascii格式的文本文件encoding-ascii.txt,内容为'编码'。对该文本读取:

#coding=utf-8

f = open('encoding-ascii.txt')
s = f.read()
f.close()

print type(s) #<type 'str'>
print(s)      #编码

u = s.decode('gb2312')
print type(u) #<type 'unicode'>
print u       #编码

上面是正确的编码处理方式。假如读取保存为utf-8格式的文件encoding-utf8.txt会出现什么情况:

#coding=utf-8

f = open('encoding-utf8.txt')
s = f.read()
f.close()

print type(s) #<type 'str'>
print(s)      #锘跨紪鐮     (乱码)


u = s.decode('gb2312') #报错。正确方式:s.decode('utf-8')
# UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 4-5: llegal multibyte sequence
print type(u) 
print u

故正确处理方式为:
使用codecs模块中的open方法,可指定编码方式打开文件,该方法返回的是unicode(注意:内置open返回的是str):

#coding=utf-8

import codecs
f = codecs.open('encoding-utf8.txt', encoding='utf-8')
u = f.read()
f.close()

print type(u) #<type 'unicode'>
print repr(u) #u'\u7f16\u7801'

s = u.encode('gb2312')
print type(s) #<type 'str'>
print s       #编码

写文件

内置的write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。

#coding=utf-8

u = u'编码'
f = open('test.txt', 'w')
# 编码成UTF-8编码的str
s = u.encode('gb2312')
f.write(s)
f.close()

查看test.txt文件,可得保存的格式为ascii。

codecs模块写入时,如果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操作。相对内置的open()来说,这个方法比较不容易在编码上出现问题。

#coding=utf-8

import codecs
u = u'编码'
f1 = codecs.open('test1.txt', 'a', encoding='gb2312') #utf-8
# 写入unicode
f1.write(u)
print type(f1)

# 写入str,自动进行解码编码操作(貌似没法自动转化TODO)
# GBK编码的str
s = '汉'
print repr(s) # '\xba\xba'
# 这里会先将GBK编码的str解码为unicode再编码为UTF-8写入
f1.write(s.decode('utf-8'))
f1.close()

网络(HTTP)操作

[传送门][]

sys/locale模块关于编码相关的方法

import sys
import locale

def p(f):
    print '%s.%s(): %s' % (f.__module__, f.__name__, f())

# 返回当前系统所使用的默认字符编码
p(sys.getdefaultencoding)

# 返回用于转换Unicode文件名至系统文件名所使用的编码
p(sys.getfilesystemencoding)

# 获取默认的区域设置并返回元祖(语言, 编码)
p(locale.getdefaultlocale)

# 返回用户设定的文本数据编码
# 文档提到this function only returns a guess
p(locale.getpreferredencoding)

# \xba\xba是'汉'的GBK编码
# mbcs是不推荐使用的编码,这里仅作测试表明为什么不应该用
print r"'\xba\xba'.decode('mbcs'):", repr('\xba\xba'.decode('mbcs'))

#在笔者的Windows上的结果(区域设置为中文(简体, 中国))
#sys.getdefaultencoding(): ascii
#sys.getfilesystemencoding(): mbcs
#locale.getdefaultlocale(): ('zh_CN', 'cp936')
#locale.getpreferredencoding(): cp936
#'\xba\xba'.decode('mbcs'): u'\u6c49'

总结

  • 使用字符编码声明,并且同一工程中的所有源代码文件使用相同的字符编码声明。
  • 抛弃str,全部使用unicode。u = u'编码'
  • 使用codecs.open()替代内置的open()。
  • 绝对需要避免使用的字符编码:MBCS/DBCS和UTF-16。

这里说的MBCS不是指GBK什么的都不能用,而是不要使用Python里名为'MBCS'的编码,除非程序完全不移植。
Python中编码'MBCS'与'DBCS'是同义词,指当前Windows环境中MBCS指代的编码。Linux的Python实现中没有这种编码,所以一旦移植到Linux一定会出现异常!另外,只要设定的Windows系统区域不同,MBCS指代的编码也是不一样的。

MBCS

计算机世界里很快就有了其他语言,单字节的ASCII已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,如GBxxx、BIGxxx等等,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。

这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。

MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用MBCS作为编码。在Windows中你看不到MBCS这几个字符,因为微软为了更加洋气,使用了ANSI来吓唬人,记事本的另存为对话框里编码ANSI就是MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。

参拷


Comments

comments powered by Disqus