《编写高质量Python代码的59个有效方法》——第3条:了解bytes、str与unicode的区别

第3条:了解bytes、str与unicode的区别
Python 3有两种表示字符序列的类型:bytes和str。前者的实例包含原始的8位值;后者的实例包含Unicode字符。
Python 2也有两种表示字符序列的类型,分别叫做str和unicode。与Python 3不同的是,str的实例包含原始的8位值;而unicode的实例,则包含Unicode字符。
把Unicode字符表示为二进制数据(也就是原始8位值)有许多种办法。最常见的编码方式就是UTF-8。但是大家要记住,Python 3的str实例和Python 2的unicode实例都没有和特定的二进制编码形式相关联。要想把Unicode字符转换成二进制数据,就必须使用encode方法。要想把二进制数据转换成Unicode字符,则必须使用decode方法。
编写Python程序的时候,一定要把编码和解码操作放在界面最外围来做。程序的核心部分应该使用Unicode字符类型(也就是Python 3中的str、Python 2中的unicode),而且不要对字符编码做任何假设。这种办法既可以令程序接受多种类型的文本编码(如Latin-1、Shift JIS和Big5),又可以保证输出的文本信息只采用一种编码形式(最好是UTF-8)。
由于字符类型有别,所以Python代码中经常会出现两种常见的使用情境:
开发者需要原始8位值,这些8位值表示以UTF-8格式(或其他编码形式)来编码的字符。
开发者需要操作没有特定编码形式的Unicode字符。
所以,我们需要编写两个辅助(helper)函数,以便在这两种情况之间转换,使得转换后的输入数据能够符合开发者的预期。
在Python 3中,我们需要编写接受str或bytes,并总是返回str的方法:

另外,还需要编写接受str或bytes,并总是返回bytes的方法:

在Python 2中,需要编写接受str或unicode,并总是返回unicode的方法:

另外,还需要编写接受str或unicode,并总是返回str的方法:

在Python中使用原始8位值与Unicode字符时,有两个问题要注意。
第一个问题可能会出现在Python 2里面。如果str只包含7位ASCII字符,那么unicode和str实例似乎就成了同一种类型。
可以用+操作符把这种str与unicode连接起来。
可以用等价与不等价操作符,在这种str实例与unicode实例之间进行比较。
在格式字符串中,可以用'%s'等形式来代表unicode实例。
这些行为意味着,在只处理7位ASCII的情境下,如果某函数接受str,那么可以给它传入unicode;如果某函数接受unicode,那么也可以给它传入str。而在Python 3中,bytes与str实例则绝对不会等价,即使是空字符串也不行。所以,在传入字符序列的时候必须留意其类型。
第二个问题可能会出现在Python 3里面。如果通过内置的open函数获取了文件句柄,那么请注意,该句柄默认会采用UTF-8编码格式来操作文件。而在Python 2中,文件操作的默认编码格式则是二进制形式。这可能会导致程序出现奇怪的错误,对习惯了Python 2的程序员来说更是如此。
例如,现在要向文件中随机写入一些二进制数据。下面这种用法在Python 2中可以正常运作,但在Python 3中不行。

发生上述异常的原因在于,Python 3给open函数添加了名为encoding的新参数,而这个新参数的默认值却是'utf-8'。这样在文件句柄上进行read和write操作时,系统就要求开发者必须传入包含Unicode字符的str实例,而不接受包含二进制数据的bytes实例。
为了解决这个问题,我们必须用二进制写入模式('wb')来开启待操作的文件,而不能像原来那样,采用字符写入模式('w')。按照下面这种方式来使用open函数,即可同时适配Python 2与Python 3:

从文件中读取数据的时候也有这种问题。解决办法与写入时相似:用'rb'模式(也就是二进制模式)打开文件,而不要使用'r'模式。
要点
在Python 3中,bytes是一种包含8位值的序列,str是一种包含Unicode字符的序列。开发者不能以>或+等操作符来混同操作bytes和str实例。
在Python 2中,str是一种包含8位值的序列,unicode是一种包含Unicode字符的序列。如果str只含有7位ASCII字符,那么可以通过相关的操作符来同时使用str与unicode。
在对输入的数据进行操作之前,使用辅助函数来保证字符序列的类型与开发者的期望相符(有的时候,开发者想操作以UTF-8格式来编码的8位值,有的时候,则想操作Unicode字符)。
从文件中读取二进制数据,或向其中写入二进制数据时,总应该以'rb'或'wb'等二进制模式来开启文件。

时间: 2024-09-17 04:37:02

《编写高质量Python代码的59个有效方法》——第3条:了解bytes、str与unicode的区别的相关文章

《编写高质量Python代码的59个有效方法》——导读

目 录 前 言 致 谢第1章 用Pythonic方式来思考 第1条:确认自己所用的Python版本 第2条:遵循PEP 8风格指南 第3条:了解bytes.str与unicode的区别第4条:用辅助函数来取代复杂的表达式第5条:了解切割序列的办法第6条:在单次切片操作内,不要同时指定start.end和stride第7条:用列表推导来取代map和f?ilter 第8条:不要使用含有两个以上表达式的列表推导第9条:用生成器表达式来改写数据量较大的列表推导第10条:尽量用enumerate取代ran

《编写高质量Python代码的59个有效方法》——第2条:遵循PEP 8风格指南

第2条:遵循PEP 8风格指南 <Python Enhancement Proposal #8>(8号Python增强提案)又叫PEP 8,它是针对Python代码格式而编订的风格指南.尽管可以在保证语法正确的前提下随意编写Python代码,但是,采用一致的风格来书写可以令代码更加易懂.更加易读.采用和其他Python程序员相同的风格来写代码,也可以使项目更利于多人协作.即便代码只会由你自己阅读,遵循这套风格也依然可以令后续的修改变得容易一些. PEP 8列出了许多细节,以描述如何撰写清晰的P

《编写高质量Python代码的59个有效方法》——第8条:不要使用含有两个以上表达式的列表推导

第8条:不要使用含有两个以上表达式的列表推导除了基本的用法(参见本书第7条)之外,列表推导也支持多重循环.例如,要把矩阵(也就是包含列表的列表,即二维列表)简化成一维列表,使原来的每个单元格都成为新列表中的普通元素.这个功能采用包含两个for表达式的列表推导即可实现,这些for表达式会按照从左至右的顺序来评估. 上面这个例子简单易懂,这就是多重循环的合理用法.还有一种包含多重循环的合理用法,那就是根据输入列表来创建有两层深度的新列表.例如,我们要对二维矩阵中的每个单元格取平方,然后用这些平方值构

《编写高质量Python代码的59个有效方法》——第15条:了解如何在闭包里使用外围作用域中的变量

第15条:了解如何在闭包里使用外围作用域中的变量 假如有一份列表,其中的元素都是数字,现在要对其排序,但排序时,要把出现在某个群组内的数字,放在群组外的那些数字之前.这种用法在绘制用户界面时候可能会遇到,我们可以用这个办法把重要的消息或意外的事件优先显示在其他内容前面. 实现该功能的一种常见做法,是在调用列表的sort方法时,把辅助函数传给key参数.这个辅助函数的返回值,将会用来确定列表中各元素的顺序.辅助函数可以判断受测元素是否处在重要群组中,并据此返回相应的排序关键字(sort key).

《编写高质量Python代码的59个有效方法》——第12条:不要在for和while循环后面写else块

第12条:不要在for和while循环后面写else块 Python提供了一种很多编程语言都不支持的功能,那就是可以在循环内部的语句块后面直接编写else块. 奇怪的是,这种else块会在整个循环执行完之后立刻运行.既然如此,那它为什么叫做else呢?为什么不叫and?在if/else语句中,else的意思是:如果不执行前面那个if块,那就执行else块.在try/except语句中,except的定义也类似:如果前面那个try块没有成功执行,那就执行except块. 同理,try/except

《编写高质量Python代码的59个有效方法》——第13条:合理利用try/except/else/f?inally结构中的每个代码块

第13条:合理利用try/except/else/f?inally结构中的每个代码块Python程序的异常处理可能要考虑四种不同的时机.这些时机可以用try.except.else和f?inally块来表述.复合语句中的每个块都有特定的用途,它们可以构成很多种有用的组合方式(参见本书第51条).1.?f?inally块如果既要将异常向上传播,又要在异常发生时执行清理工作,那就可以使用try/f?inally结构.这种结构有一项常见的用途,就是确保程序能够可靠地关闭文件句柄(还有另外一种写法,参见

《编写高质量Python代码的59个有效方法》——第17条:在参数上面迭代时,要多加小心

第17条:在参数上面迭代时,要多加小心如果函数接受的参数是个对象列表,那么很有可能要在这个列表上面多次迭代.例如,要分析来美国Texas旅游的人数.假设数据集是由每个城市的游客数量构成的(单位是每年百万人).现在要统计来每个城市旅游的人数,占总游客数的百分比.为此,需要编写标准化函数(normalization function).它会把所有的输入值加总,以求出每年的游客总数.然后,用每个城市的游客数除以总数,以求出该城市所占的比例. 把各城市的游客数量放在一份列表里,传给该函数,可以得到正确结

《编写高质量Python代码的59个有效方法》——第7条:用列表推导来取代map和f?ilter

第7条:用列表推导来取代map和f?ilterPython提供了一种精练的写法,可以根据一份列表来制作另外一份.这种表达式称为list comprehension(列表推导).例如,要用列表中每个元素的平方值构建另一份列表.如果采用列表推导来实现,那就同时指定制作新列表时所要迭代的输入序列,以及计算新列表中每个元素的值时所用的表达式. 除非是调用只有一个参数的函数,否则,对于简单的情况来说,列表推导要比内置的map函数更清晰.如果使用map,那就要创建lambda函数,以便计算新列表中各个元素的

《编写高质量Python代码的59个有效方法》——第20条:用None和文档字符串来描述具有动态默认值的参数

第20条:用None和文档字符串来描述具有动态默认值的参数有时我们想采用一种非静态的类型,来做关键字参数的默认值.例如,在打印日志消息的时候,要把相关事件的记录时间也标注在这条消息中.默认情况下,消息里面所包含的时间,应该是调用log函数那一刻的时间.如果我们以为参数的默认值会在每次执行函数时得到评估,那可能就会写出下面这种代码. 两条消息的时间戳(timestamp)是一样的,这是因为datetime.now只执行了一次,也就是它只在函数定义的时候执行了一次.参数的默认值,会在每个模块加载进来