我确定有很多关于Unicode和Python的说明,但为了方便自己的理解使用,我还是打算再写一些关于它们的东西。
字节流 vs Unicode对象
我们先来用Python定义一个字符串。当你使用string类型时,实际上会储存一个字节串。
[ a ][ b ][ c ] = "abc" [ 97 ][ 98 ][ 99 ] = "abc"</div>
在这个例子里,abc这个字符串是一个字节串。97.,98,,99是ASCII码。Python 2.x版本的一个不足之处就是默认将所有的字符串当做ASCII来对待。不幸的是,ASCII在拉丁式字符集里是最不常见的标准。
ASCII是用前127个数字来做字符映射。像windows-1252和UTF-8这样的字符映射有相同的前127个字符。在你的字符串里每个字节的值低于127的时候是安全的混合字符串编码。然而作这个假设是件很危险的事情,下面还将会提到。
当你的字符串里有字节的值大于126的时候就会出现问题了。我们来看一个用windows-1252编码的字符串。Windows-1252里的字符映射是8位的字符映射,那么总共就会有256个字符。前127个跟ASCII是一样的,接下来的127个是由windows-1252定义的其他字符。
A windows-1252 encoded string looks like this: [ 97 ] [ 98 ] [ 99 ] [ 150 ] = "abc–"</div>
Windows-1252仍然是一个字节串,但你有没有看到最后一个字节的值是大于126的。如果Python试着用默认的ASCII标准来解码这个字节流,它就会报错。我们来看当Python解码这个字符串的时候会发生什么:
>>> x = "abc" + chr(150) >>> print repr(x) 'abc\x96' >>> u"Hello" + x Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: 'ASCII' codec can't decode byte 0x96 in position 3: ordinal not in range(128)</div>
我们来用UTF-8来编码另一个字符串:
A UTF-8 encoded string looks like this: [ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–" [0x61] [0x62] [0x63] [0xe2] [ 0x80] [ 0x93] = "abc-"</div>
如果你拿起看你熟悉的Unicode编码表,你会发现英文的破折号对应的Unicode编码点为8211(0×2013)。这个值大于ASCII最大值127。大于一个字节能够存储的值。因为8211(0×2013)是两个字节,UTF-8必须利用一些技巧告诉系统存储一个字符需要三个字节。我们再来看当Python准备用默认的ASCII来编码一个里面有字符的值大于126的UTF-8编码字符串。
>>> x = "abc\xe2\x80\x93" >>> print repr(x) 'abc\xe2\x80\x93' >>> u"Hello" + x Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeDecodeError: 'ASCII' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)</div>
你可以看到,Python一直是默认使用ASCII编码。当它处理第4个字符的时候,因为它的值为226大于126,所以Python抛出了错误。这就是混合编码所带来的问题。
解码字节流
在一开始学习Python Unicode 的时候,解码这个术语可能会让人很疑惑。你可以把字节流解码成一个Unicode对象,把一个Unicode 对象编码为字节流。
Python需要知道如何将字节流解码为Unicode对象。当你拿到一个字节流,你调用它的“解码方法来从它创建出一个Unicode对象。
你最好是尽早的将字节流解码为Unicode。
>>> x = "abc\xe2\x80\x93"
>>> x = x.decode("utf-8")
>>> print type(x)
<type 'unicode'>
>>> y = "abc" + chr(150)
>>> y = y.decode("windows-1252")
>>> print type(y)
>>> print x + y
abc–abc–
</div>
将Unicode编码为字节流
Unicode对象是一个文本的编码不可知论的代表。你不能简单地输出一个Unicode对象。它必须在输出前被变成一个字节串。Python会很适合做这样的工作,尽管Python将Unicode编码为字节流时默认是适用ASCII,这个默认的行为会成为很多让人头疼的问题的原因。
>>> u = u"abc\u2013"
>>> print u
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2013' in position 3: ordinal not in range(128)
>>> print u.encode("utf-8")
abc–
</div>
使用codecs模块
codecs模块能在处理字节流的时候提供很大帮助。你可以用定义的编码来打开文件并且你从文件里读取的内容会被自动转化为Unicode对象。
试试这个:
>>> import codecs
>>> fh = codecs.open("/tmp/utf-8.txt", "w", "utf-8")
>>> fh.write(u"\u2013")
>>> fh.close()
</div>
它所做的就是拿到一个Unicode对象然后将它以utf-8编码写入到文件。你也可以在其他的情况下这么使用它。
试试这个:
当从一个文件读取数据的时候,codecs.open 会创建一个文件对象能够自动将utf-8编码文件转化为一个Unicode对象。
我们接着上面的例子,这次使用urllib流。
>>> stream = urllib.urlopen("http://www.google.com")
>>> Reader = codecs.getreader("utf-8")
>>> fh = Reader(stream)
>>> type(fh.read(1))
<type 'unicode'>
>>> Reader
<class encodings.utf_8.StreamReader at 0xa6f890>
</div>
单行版本:
>>> fh = codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com"))
>>> type(fh.read(1))
</div>
你必须对codecs模块十分小心。你传进去的东西必须是一个Unicode对象,否则它会自动将字节流作为ASCII进行解码。
>>> x = "abc\xe2\x80\x93" # our "abc-" utf-8 string
>>> fh = codecs.open("/tmp/foo.txt", "w", "utf-8")
>>> fh.write(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/codecs.py", line 638, in write
return self.writer.write(data)
File "/usr/lib/python2.5/codecs.py", line 303, in write
data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)
</div>
哎呦我去,Python又开始用ASCII来解码一切了。
将UTF-8字节流切片的问题
因为一个UTF-8编码串是一个字节列表,len( )和切片操作无法正常工作。首先用我们之前用的字符串。
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"</div>
接下来做以下的:
>>> my_utf8 = "abc–" >>> print len(my_utf8) 6</div>
神马?它看起来是4个字符,但是len的结果说是6。因为len计算的是字节数而不是字符数。
>>> print repr(my_utf8) 'abc\xe2\x80\x93'</div>
现在我们来切分这个字符串。
>>> my_utf8[-1] # Get the last char '\x93'</div>
我去,切分结果是最后一字节,不是最后一个字符。
为了正确的切分UTF-8,你最好是解码字节流创建一个Unicode对象。然后就能安全的操作和计数了。
>>> my_unicode = my_utf8.decode("utf-8")
>>> print repr(my_unicode)
u'abc\u2013'
>>> print len(my_unicode)
4
>>> print my_unicode[-1]
–
</div>
当Python自动地编码/解码
在一些情况下,当Python自动地使用ASCII进行编码/解码的时候会抛出错误。
第一个案例是当它试着将Unicode和字节串合并在一起的时候。
<

