返回介绍

建议17:考虑兼容性 尽可能使用 Unicode

发布于 2024-01-30 22:19:09 字数 6880 浏览 0 评论 0 收藏 0

Python内建的字符串有两种类型:str和Unicode,它们拥有共同的祖先basestring。其中Unicode是Python2.0中引入的一种新的数据类型,所有的Unicode字符串都是Unicode类型的实例。创建一个Unicode字符相对简单。

>>> strUnicode = u"unicode 
字符串" #
前面加u
表示Unicode
>>> strUnicode
u'unicode \u5b57\u7b26\u4e32'
>>> print strUnicode
unicode 
字符串
>>> type(strUnicode)
<type 'unicode'>
>>> type(strUnicode).__bases__
(<type 'basestring'>,)

Python中为什么需要加入对Unicode的支持呢?我们先来了解一下Unicode相关的背景知识。

在Unicode之前,最早的ASCII编码用一个字节(8bit,最高位为0)只能表示128个字符,如英文大小写字符、数字以及其他符号等。但世界上显然不只有一种语言,不同种语言所包含的字符数量也不相同,对于很多语言来说128个字符数是远远不够的,即使对ASCII进行扩展,256个字符也不能满足要求。于是出现了各种不同的字符编码系统,如我国表示汉字编码的GBK。但这又引入了一个新的问题:不同编码系统之间存在冲突。在两种不同的编码系统中,相同的编码可能代表不同的意义或者不同的编码代表相同的字符,从而导致不同平台、不同语言之间的文本无法很好地进行转换。比如,“我”字在GB2312中表示为0x4650,而繁体中文Big5中的编码为0XA7DA,而0XA7DA在GB2312中却表示“牋”,乱码由此产生。要解决这个问题,必须为不同的文字分配统一编码,Unicode(Universal Multiple-Octet Coded Character Set)由此产生,它也被称作万国码,Unicode为每种语言设置了唯一的二进制编码表示方式,提供从数字代码到不同语言字符集之间的映射,从而可以满足跨平台、跨语言之间的文本处理要求。

Unicode编码系统可以分为编码方式和实现方式两个层次。在编码方式上,分为UCS-2和UCS-4两种方式,UCS-2用两个字节编码,UCS-4用4个字节编码。目前实际应用的统一码版本对应于UCS-2,使用16位的编码空间。一个字符的Unicode编码是确定的,但是在实际传输过程中,由于系统平台的不同以及出于节省空间的目的,实现方式有所差异。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format),简称为UTF,包括UTF-7、UTF-16、UTF-32,UTF-8等,其中较为常见的为UTF-8。UTF-8的特点是对不同范围的字符使用不同长度的编码,其中0x00~0x7F的字符的UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节,从Unicode到UTF-8的编码方式如表2-3所示。

表2-3 Unicode到UTF-8的编码方式

Unicode经过几十年的发展,逐渐成为业界标准,许多相关技术都提供Unicode支持,如XML、Java、LDAP、CORBA 3.0等,Python也不例外。更多Unicode的知识读者可以查看http://www.unicode.org/。

在了解完Unicode的背景知识之后再来看看Python中处理中文字符经常会遇见的以下几个问题:

示例一 读出文件的内容显示为乱码。

filehandle = open("test.txt",'r')
print filehandle.read()
filehandle.close()

其中,文件test.txt中的内容为“python中文测试”,文件以UTF-8的形式保存。

运行程序结果如下:

python
涓 
枃娴嬭瘯 

示例二 当Python源文件中包含中文字符的时候抛出SyntaxError异常。

unicodetest.py文件的内容如下:

s = "python 
中文测试"
print s

上述程序运行时报错如下:

  File "unicodetest.py", line 1
SyntaxError: Non-ASCII character '\xd6' in file unicodetest.py on line 1, but no
 encoding declared; see http://www.python.org/peps/pep-0263.html for details

示例三 普通字符和Unicode进行字符串连接的时候抛出UnicodeDecodeError异常。

# coding=utf-8
s = "
中文测试"+ u"Chinese Test"
print s

程序运行抛出异常如下:

Traceback (most recent call last):
  File "tests.py", line 2, in <module>
  s = "
中文测试"+ u"Chinese Test"
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 0: ordinal
not in range(128)

来一一分析上面例子产生错误的原因以及如何在不同的编码和Unicode之间进行转换。

示例一分析:读入的文件test.txt用UTF-8编码形式保存,但是Windows的本地默认编码是CP936,在Windows系统中它被映射为GBK编码,所以当在控制台上直接显示UTF-8字符的时候,这两种编码并不兼容,以UTF-8形式表示的编码在GBK编码中被解释成为其他的符号,由此便产生了乱码。通过上面的背景知识了解到Unicode为不同语言设置了唯一的二进制表示形式,可以轻易地解决不同字符集之间的字符映射问题,因此要解决示例一的乱码问题可以使用Unicode作为中间介质来完成转换。首先需要对读入的字符用UTF-8进行解码,然后再用GBK进行编码。修改后的结果如下:

filehandle = open("test.txt",'r')
print (filehandle.read().decode("utf-8")).encode("gbk")  ......   
①
filehandle.close()

输出为:

python
中文测试

代码标注①处分别使用了decode()和encode()方法,这两个方法的作用分别是对字符串进行解码和编码。其中decode()方法将其他编码对应的字符串解码成Unicode,而encode()方法将Unicode编码转换为另一种编码,Unicode作为转换过程中的中间编码。decode()和encode()方法的函数形式如下:

str.decode([
编码参数[, 
错误处理]])
str.encode([
编码参数[, 
错误处理]])

常见的编码参数如表2-4所示。

表2-4 常见编码参数

错误处理参数有以下3种常用方式:

1)strict':默认处理方式,编码错误抛出UnicodeError异常。

2)ignore'忽略不可转换字符。

3)replace'将不可转换字符用?代替。

对于A、B两种编码系统,两者之间的相互转换示意图如图2-1所示。

图2-1 编码转换示意图

标注①处filehandle.read()读出来的字符串是用UTF-8表示的,也就是A表示为UTF-8,使用decode()方法解码得到Unicode,对应上图箭头1所示过程:filehandle.read().decode("utf-8")。然后再使用encode方法将Unicode转换为为GBK的表示形式,如果unicodestr= filehandle.read().decode("utf-8")的话,则unicodestr.encode("gbk")表示箭头2所示过程。所以从A到B对应的代码为:

(filehandle.read().decode("utf-8")).encode("gbk")

提醒:上面的例子在某些有些情况下(如test.txt是用Notepad软件以UTF-8编码形式保存)可能还会出现如下异常:

  print (filehandle.read().decode("utf-8")).encode("gbk")
UnicodeEncodeError: 'gbk' codec can't encode character u'\ufeff' in position 0:
illegal multibyte sequence

这是因为有些软件在保存UTF-8编码的时候,会在文件最开始的地方插入不可见的字符BOM(0xEF 0xBB 0xBF,即BOM),这些不可见字符无法被正确的解析,而利用codecs模块可以方便地处理这种问题。

import codecs
content = open("test.txt",'r').read()
filehandle.close()
if content[:3] == codecs.BOM_UTF8:#
如果存在BOM
字符则去掉
content = content[3:]
print content.decode("utf-8")

关于BOM:

Unicode存储有字节序的问题,例如“汉”字的Unicode编码是0X6C49,如果将6C写在前面,则为big endian,将49写在前面则成为little endian。UTF-16以两个字节为编码单元,在字符的传送过程中,为了标明字节的顺序,Unicode规范中推荐使用BOM(Byte Order Mark):即在 UCS编码中用一个叫做ZERO WIDTH NO-BREAK SPACE的字符,它的编码是FEFF(该编码在UCS中不存在对应的字符),UCS规范建议在传输字节流前,先传输字符ZERO WIDTH NO-BREAK SPACE。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。 UTF-8使用字节来编码,一般不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符ZERO WIDTH NO-BREAK SPACE的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

示例二分析:Python中默认的编码是ASCII编码(这点可以通过sys.getdefaultencoding()来验证),所以unicodetest.py文件是以ASCII形式保存的,s是包含中文字符的普通字符串。当调用print方法输出的时候会隐式地进行从ASCII到系统默认编码(Windows上为CP936)的转换,中文字符并不是ASCII字符,而此时源文件中又未指定其他编码方式,Python解释器并不知道如何正确处理这种情况,便会抛出异常:SyntaxError: Non-ASCII character '\xd6' in file unicodetest.py on line 1。因此,要避免这种错误需要在源文件中进行编码声明,声明可用正则表达式

"coding[:=]\s*([-\w.]+)"表示。一般来说进行源文件编码声明有以下3种方式:

第一种声明方式:

# coding=<encoding name>

第二种声明方式:

#!/usr/bin/python
# -*- coding: <encoding name> -*-

第三种声明方式:

#!/usr/bin/python
# vim: set fileencoding=<encoding name> :

示例二在源文件头中加入编码声明# coding=utf-8便可解决问题。

示例三分析:使用+操作符来进行字符串的连接时,+左边为中文字符串,类型为str,右边为Unicode字符串。当两种类型的字符串连接的时候,Python将左边的中文字符串转换为Unicode再与右边的Unicode字符串做连接,将str转换为Unicode时使用系统默认的ASCII编码对字符串进行解码,但由于“中文测试”的ASCII编码为\xd6\xd0\xce\xc4\xb2\xe2\xca\xd4,其中“中”字的编码\xd6对应的值为214。当编码值在0~127的时候Unicode和ASCII是兼容的,转换不会有什么问题,但当其值大于128的时候,ASCII编码便不能正确处理这种情况,因而抛出UnicodeDecodeError异常。解决上面的问题有以下两种思路:

1)指定str转为Unicode时的编码方式。

# coding=utf-8
s = "
中文测试".decode('gbk') + u"Chinese Test"

2)将Unicode字符串进行UTF-8编码。

s = "
中文测试"+ u"Chinese Test".encode("utf-8")

Unicode提供了不同编码系统之间字符转换的桥梁,要避免令人头疼的乱码或者避免UnicodeDecodeError以及UnicodeEncodeError等错误,需要弄清楚字符所采用的编码方式以及正确的解码方法。对于中文字符,为了做到不同系统之间的兼容,建议直接使用Unicode表示方式。Python2.6之后可以通过import unicode_literals自动将定义的普通字符识别为Unicode字符串,这样字符串的行为将和Python3中保持一致。

>>> from __future__ import unicode_literals
>>> s = "
中文测试"
>>> s
u'\u4e2d\u6587\u6d4b\u8bd5'

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文