python编码问题(2)

 

1 编码、解码

  如同密码领域一样,从明文到密码是加密,从密码到明文是解密。在python中,(编码:unicode-->str;) 解码(str-->unicode).

      既然是编码,那么就和密码领域一样,编码和解码自然涉及到编码/解码方案(对应加密或者解密算法),unicode相当于明文。在python中,编码函数是encode(),解码函数是decode()。需要注意的一点是,如果我们调用str.encode(),这里涉及到一个隐士的类型转化,会现将str转化成unicode,才能进行编码,这也是不太容易理解的地方。所以,str.encode()实际上就等价于str.decode(sys.defaultencoding).encode().而sys.defaultencoding一般是ascii,它是不能用来编码中文字符的。  字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

  decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码。encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。代码中字符串的默认编码与代码文件本身的编码一致。

2 字符串

在Python中有两种默认的字符串:str和Unicode:

  • str字符串本质上是一个字节流,是原字符经过编码之后的一个个字节,但它并不存储具体的编码方式。
  • Unicode字符串本质上是一个Unicode对象,它内部使用了特定的编码,但是对程序员透明。事实上Python中并没有Unicode字符串,但为了理解方便,以下所说的Unicode字符串均指的是Unicode对象。

 

# -*- coding: utf-8 -*-
# file: example1.py
import string

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

print isinstance(s, str)      # True
print isinstance(u, unicode)  # True

print s.__class__   # <type 'str'>
print u.__class__   # <type 'unicode'>

如1中所说,两个 Python 字符串类型间可以用 encode / decode 方法转换

# 从 str 转换成 unicode
print s.decode('utf-8')   # 关关雎鸠

# 从 unicode 转换成 str
print u.encode('utf-8')   # 关关雎鸠

如果用错误的字符集来 encode/decode 会怎样?

# 用 ascii 编码含中文的 unicode 字符串
u.encode('ascii')  # 错误,因为中文无法用 ascii 字符集编码
                   # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

# 用 gbk 编码含中文的 unicode 字符串
u.encode('gbk')  # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示
                 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'
                 # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的

# 用 ascii 解码 utf-8 字符串
s.decode('ascii')  # 错误,中文 utf-8 字符无法用 ascii 解码
                   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

# 用 gbk 解码 utf-8 字符串
s.decode('gbk')  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码
                 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'

几个小问题

(1) 字符串拼接

# -*- coding: utf-8 -*-
# file: example2.py

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

s + u  # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。

由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 'ascii' ——显然,如果需要转换的 str 有中文,一定会出现错误。

(2)字符串长度

str类型的字符串长度为其编码后的长度,并不是字符串本身长度。Unicode字符串的类型为"unicode",使用len得到的长度为2,表示了这个字符串的真正长度。

>>> s = '你好'
>>> type(s)
<type 'str'>
>>> s
'\xc4\xe3\xba\xc3'
>>> print s
你好
>>> len(s)
4
>>> u = u'你好'
>>> type(u)
<type 'unicode'>
>>> u
u'\u4f60\u597d'
>>> print u
你好
>>> len(u)
2

为了减少不必要的麻烦,在实际的编程工作中,我们可以按如下方法进行:

  1. 最先解码。首先要将输入的字符字节流解码为Unicode对象,方便后续如计数、切片等操作的进行。
  2. 最后编码。最后将处理后的结果输出到其他文件或者数据库的时候才将Unicode对象进行编码,以满足特定的存储显示需求。
  3. 默认使用utf-8编码。使用UTF-8编码可以处理任何Unicode字符,具有更好地通用性,尽量用来替代默认的ASCII或者GBK编码,但是要注意Windows中默认编码为GBK或者其他类似GBK的编码的情况

3 文件编码

  文件编码指的是具体的代码文本在保存时使用的编码方法。如:s='中文',如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。

  如果声明的编码(指-*- coding: UTF-8 -*-这样的申明)和文件实际的编码不一致的话,很可能会出现乱码甚至出错等问题。原因是对于类似于u’你好’这样声明的字符串,在源代码文件里经过了该文件指定编码方法的编码被保存成字节流,程序运行时读取到这个字节流,就会使用编码声明中的编码去解码得到原始字符串,然后对这个原始字符串再用Unicode的内部编码方案进行存储。而对于’你好’这样不带u的字符串,则表示字节流,此时会直接用文件编码后的字节数组进行表示,不需要使用编码声明中的编码方案进行解码。例如,建立如下内容的脚本文件然后保存成ANSI编码:

# -*- coding: UTF-8 -*-

s = '你好'

print repr(s), s

u = u'你好'

print repr(u), u

 

运行后得到错误

SyntaxError: (unicode error) ‘utf8′ codec can’t decode byte 0xc4 in position 0: invalid continuation byte

但把脚本改成:

# -*- coding: UTF-8 -*-

s = '你好'

print repr(s), s

则可以得到以下结果:

'\xc4\xe3\xba\xc3' 你好 #原文件使用的是ANSI编码,所以得到GBK编码,GBK编码可以正常显示

 

 

 参考资料

http://in355hz.iteye.com/blog/1860787

http://5319188.blog.51cto.com/5309188/1112505

http://noalgo.info/578.html

 

posted on 2015-10-08 15:06  月下之风  阅读(455)  评论(0编辑  收藏  举报

导航