Item 3: Know the Differences Between bytes and str(了解字节和str之间的区别)

在Python中,有两种类型表示字符数据序列:bytes和str. 字节的实例包含原始数据,无符号的8位值(通常以ASCII编码显示):

>>> a = b'h\x65llo'
>>> a
b'hello'
>>> print(list(a))
[104, 101, 108, 108, 111]
>>> print(a)
b'hello'
>>> 

str的实例包含表示人类语言文本字符的Unicode编码。

>>> a = 'a\u0300 propos'
>>> print(list(a))
['a', '̀', ' ', 'p', 'r', 'o', 'p', 'o', 's']
>>> print(a)
à propos

重要的是,str实例没有相关联的二进制编码,而bytes实例也没有相关联的文本编码.

  • 要将Unicode数据转换为二进制数据,必须调用str的encode方法。
  • 要将二进制数据转换为Unicode,必须调用bytes的 decode 方法

您可以显式地指定要为这些方法使用的编码,或者接受系统默认值,通常是UTF-8(但不总是这样—请参阅下面的详细信息)。

在编写Python程序时,在接口的最远边界处对Unicode数据进行编码和解码是很重要的;这种方法通常称为Unicode sandwich.程序的核心应该使用包含Unicode数据的str类型,并且不应该假设有任何字符编码. 这种方法允许您非常接受其他文本编码(例如Latin-1、Shift JIS和Big5),同时严格控制输出文本编码(理想情况下是UTF-8)。

字符类型之间的分隔方式导致了Python代码中两种常见的情况:

  • 您希望对包含utf -8编码的字符串(或其他编码)的原始8位序列进行操作。
  • 您希望对没有特定编码的Unicode字符串进行操作。

通常需要两个辅助函数来在这些情况之间进行转换,并确保输入值的类型符合代码的期望。

第一个函数接受一个字节或str实例并总是返回一个str

def to_str(bytes_or_str):
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode('utf-8')
    else:
        value = bytes_or_str
    return value  # Instance of str

print(repr(to_str(b'foo')))
print(repr(to_str('bar')))

第一个函数接受一个字节或str实例并总是返回一个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  # Instance of bytes

print(repr(to_bytes(b'foo')))
print(repr(to_bytes('bar')))

在Python中处理原始的8位值和Unicode字符串时有两个大问题
第一个问题是字节和str的工作方式似乎相同,但它们的实例彼此不兼容,所以你必须考虑你所传递的字符序列的类型。

>>> print(b'one'+b'two')
b'onetwo'
>>> print('one'+'two')
onetwo

通过使用+运算符,可以将bytes + bytes ,将str + str,但是不能 bytes + str

>>> print('one'+b'two')
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can only concatenate str (not "bytes") to str

通过使用二进制操作符,可以比较字节与字节以及str

>>> assert 'red' > 'blue'
>>> assert b'red' > b'blue'

但是 不能 直接比较 str 和 bytes

>>> assert b'red' > 'blue'
Traceback (most recent call last):
  File "", line 1, in 
TypeError: '>' not supported between instances of 'bytes' and 'str'

比较字节和str实例是否相等总是会计算为False,即使它们包含完全相同的字符

>>> print(b'foo' == 'foo')
False

%处理各种类型的字符串格式化

>>> print(b'red %s'%b'blue')
b'red blue'
>>> print('red %s'%'blue')
red blue

但是您不能将一个str实例传递给一个字节格式字符串,因为Python不知道使用什么二进制文本编码

>>> print(b'red %s'%'blue')
Traceback (most recent call last):
  File "", line 1, in 
TypeError: %b requires a bytes-like object, or an object that implements __bytes__, not 'str'

您可以使用%操作符将一个bytes实例传递给一个str格式字符串,但它不会执行您所期望的操作

>>> print('red %s'%b'blue')
red b'blue'

这段代码实际上在bytes实例上调用了__repr__方法(参见第75项:“使用repr字符串调试输出”),并替换了%s,这就是为什么b'blue'在输出中仍然转义的原因。

第二个问题是涉及文件句柄的操作(由open内置函数返回)默认需要Unicode字符串,而不是原始字节
这可能会导致意想不到的失败,尤其是对熟悉python2的语法的专业人士。例如,假设我要将一些二进制数据写入文件。这个看似简单的代码会中断

>>> with open('data.bin', 'w') as f:
...     f.write(b'\xf1\xf2\xf3\xf4\xf5')
... 
Traceback (most recent call last):
  File "", line 2, in 
TypeError: write() argument must be str, not bytes

异常的原因是 默认以 mode('w') 文本模式而不是用 mode('wb') 二进制模式 打开。 文本模式期望的输入是str, 二进制是bytes

>>> with open('data.bin', 'wb') as f:
...     f.write(b'\xf1\xf2')
... 
2

类似的 在 读文件也同样存在。

或者,我可以显式地为open函数指定编码参数,以确保我不会对任何特定于平台的行为感到惊讶。例如,这里我假设文件中的二进制数据实际上是编码为“cp1252”的字符串(一种遗留的Windows编码)

>>> with open('data.bin', 'r', encoding='cp1252') as f:
...     data = f.read()
... 
>>> data
'ñò'

异常消失了,文件内容的字符串解释与读取原始字节时返回的内容非常不同

这里的教训是,您应该检查系统上的默认编码(使用python3 -c 'import locale; print(locale.getpreferredencoding())')来理解它与您的期望有什么不同。

当有疑问时,您应该显式地将编码参数传递给open。

值得注意的

  • 字节包含8位值的序列,str包含Unicode编码的序列
  • 使用辅助函数确保操作的输入是预期的字符序列类型(8位值、utf -8编码的字符串、Unicode编码等)。
  • 能将字节和str实例与操作符(如>、==、+和%)一起使用。
  • 如果你想从一个文件读写二进制数据,总是使用二进制模式打开文件(比如'rb'或'wb')。
  • 如果您想在文件中读取或写入Unicode数据,请注意系统的默认文本编码.如果希望避免意外,则显式地将编码参数传递给open

你可能感兴趣的:(Item 3: Know the Differences Between bytes and str(了解字节和str之间的区别))