Python:了解bytes、str 与 unicode 的区别

【精选秒杀】热卖云产品限量抢,云服务器20元/月起,服务稳定,价格更优

节选自:Effective Python 编写高质量Python代码的59个有效方法 – 码农电子书!  

第1章 用 Pythonic 方式来思考

Python3中,有两种类型的字符代表序列:bytes(字节)和 str(字符串)。字节的实例包含8个原生的比特值,而字符串的实例则是用Unicode字符来堆砌的。

Python2中,有两种类型的字符代表序列:str(字符串) 和 unicode(Unicode字符)。与Python3相反,字符串实例代表着原生的8比特值序列,而unicode则由Unicode字符堆砌而成。

有许多方法来表示Unicode字符的二进制数据(原生的8比特值 序列)。最常用的编码方式为UTF-8编码。还有一点很重要,那就是Python3中的字符串实例和Python2中的unicode实例并没有相关联的二进制编码。所以要想将Unicode字符转换成二进制数据,就必须使用encode方法,反过来,要想把二进制数据转换成Unicode字符,就必须使用decode方法。

当你开始写Python程序的时候,在接口的最开始位置声明对Unicode的编码解码的细节很重要。在你的代码中,最核心的部分应使用Unicode字符类型(Python3中使用str,Python2中使用unicode)并且不应该考虑关于字符编码的任何其他方式。本文允许你使用自己喜欢的可替代性的文本编码方式(如Latin-1,Shift JISBig5),但是应该对你的文本输出编码严格的限定一下(理想的方式是使用UTF-8编码)。

由于字符类型的不同,导致了Python代码中出现了两种常见的情形的发生。

  • 你想操作UTF-8(或者其他的编码方式)编码的8比特值 序列。
  • 你想操作没有特定编码的Unicode字符。 所以你通常会需要两个工具函数来对这两种情况的字符进行转换,以此来确保输入值符合代码所预期的字符类型。

Python3

  • 你将需要一个方法,接收str或者bytes,总是来返回str类型的数据。如下:
def to_str(bytes_or_str):
    if isinstance(bytes_or_str,bytes):
        value = bytes_or_str.encode('utf-8')
    else:
        value = bytes_or_str
    # str类型的数据
    return value
  • 同理,我们需要另一个方法,来接收str 或 bytes ,总是来返回bytes类型的数据。
def to_bytes(bytes_or_str):
    if isinstance(bytes_or_str,str):
        value = bytes_or_str.encode('utf-8')
    else:
        value = bytes_or_str
    # 字节类型的数据
    return value

Python2

  • 需要一个方法,来接收str或者unicode类型的数据,总是来返回unicode类型的数据。
def to_unicode(unicode_or_str):
    if isinstance(unicode_or_str,str):
        value = unicode_or_str.encode('utf-8')
    else:
        value = unicode_or_str
    # unicode类型的数据
    return value
  • 同理,需要一个接收str或者unicode类型的数据,总是来返回str类型的数据。
def to_str(unicode_or_str):
    if isinstance(unicode_or_str,unicode):
        value = unicode_or_str.encode('utf-8')
    else:
        value = unicode_or_str
    # str类型的数据
    return value

两大陷阱

Python中处理原生的8比特值 序列以及Unicode字符的时候,有两大陷阱。

一个是在Python2中,当一个str数据仅仅包含7比特的ASCII码字符的时候,unicodestr实例看起来是一致的。

  • 可以使用‘+’运算符和合并str和unicode。
  • 可以使用等价或者不等价运算符来比较str和unicode实例。
  • 可以使用unicode来代换 像‘%s’这种字符串中的格式化占位符。

以上行为意味着,如果你的代码中仅仅处理原生的7比特序列,那么便可以不必在意是str类型的数据还是unicode类型的数据了。在Python3中,bytesstr实例是不可能等价的,即使是空的字符串也不可能等价。所以你必须谨慎地对正在处理的代码进行字符类别的区分处理。

另一个是在Python3中,涉及到文件处理的操作(使用内置的open函数)会默认的以UTF-8进行编码。而在Python2中默认采用二进制形式来编码。这也是导致意外事故发生的根源,特别是对于那些更习惯于使用Python2的程序员而言。

比方说,你想将几个随机的二进制数据写入到一个文件中。在Python2中,下面的这段代码可以正常的工作,但是在Python3中却会报错并退出。详细信息如下。

def open('/tmp/random.bin','w') as f:
    f.write(os.urandom(10))

>>>
TypeError: must be str,not bytes

导致这个异常发生的原因是在Python3中对于open函数又新增了一个名为encoding的参数。此参数默认为UTF-8。这使得其对于文件的读写操作预期的源为包含了Unicode字符串的str实例,而不是包含了二进制数据的字节文件。

为了使得上面的函数正常的工作,我们必须指明被操作的数据是以‘wb’模式打开,而不是简单的‘w’模式。这里,作者介绍了一个在Python2Python3 中都通用的方法,详细如下。

with open('/tmp/random.bin','wb) as f:
    f.write(os.urandom(10))

好了,写文件的问题算是解决了,但是不要忘了还有读文件的问题哦。同样的我们也只需要改变一下读文件的模式即可。即‘r’换成‘rb’。


要点:

  • Python3中,字节包含的是8个比特值的序列,str是包含Unicode的字符的串。字节和字符串实例不能同时出现在操作符‘>’ 或者 ‘+’中。
  • Python2中,str是包含8个比特值的序列,unicode是包含Unicode字符的串,二者可以同时出现在只包含7个比特的ASCII码的运算中。
  • 使用工具函数来确保程序输入的数据时程序预期的类型。
  • 总是使用‘wb’和‘rb’模式来写文件和读文件。