- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议17:考虑兼容性 尽可能使用 Unicode
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论