为什么需要'b'来用Base64编码字符串?

351 投票
5 回答
575758 浏览
提问于 2025-04-17 10:30

根据这个Python示例,我用下面的代码把一个字符串编码成Base64:

>>> import base64
>>> encoded = base64.b64encode(b'data to be encoded')
>>> encoded
b'ZGF0YSB0byBiZSBlbmNvZGVk'

但是,如果我不加前面的b

>>> encoded = base64.b64encode('data to be encoded')

我就会遇到以下错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python32\lib\base64.py", line 56, in b64encode
   raise TypeError("expected bytes, not %s" % s.__class__.__name__)
   TypeError: expected bytes, not str

这是为什么呢?

5 个回答

39

如果你要编码的数据里包含一些“奇怪”的字符,我觉得你需要使用“UTF-8”编码。

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
234

简短回答

你需要把一个类似于 bytes 的对象(比如 bytesbytearray 等)传给 base64.b64encode() 方法。这里有两种方法:

>>> import base64
>>> data = base64.b64encode(b'data to be encoded')
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

或者用一个变量:

>>> import base64
>>> string = 'data to be encoded'
>>> data = base64.b64encode(string.encode())
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

为什么?

在 Python 3 中,str 对象并不是 C 语言那种字符数组(所以它们不是字节数组),而是一种没有固定编码的数据结构。你可以用多种方式来编码这个字符串(或者说解释它)。最常见的方式(也是 Python 3 的默认方式)是 utf-8,特别是因为它和 ASCII 兼容(虽然大多数常用编码都是这样)。当你对一个 string 调用 .encode() 方法时,Python 会把这个字符串按照 utf-8(默认编码)进行解释,并给你返回对应的字节数组。

Python 3 中的 Base-64 编码

最开始问题的标题是关于 Base-64 编码的。接下来我们来聊聊 Base-64 的内容。

base64 编码把 6 位的二进制数据块用字符 A-Z、a-z、0-9、'+'、'/' 和 '=' 来编码(有些编码用不同的字符代替 '+' 和 '/')。这是一种基于数学构造的字符编码,叫做 radix-64 或 base-64 数字系统,但它们是非常不同的。数学中的 Base-64 是一种像二进制或十进制的数字系统,你可以对整个数字进行基数转换,或者(如果你转换的基数是小于 64 的 2 的幂)从右到左分块进行转换。

base64 编码中,翻译是从左到右进行的;前 64 个字符就是为什么它叫 base64 编码。第 65 个 '=' 符号是用来填充的,因为编码是以 6 位为块,但通常要编码的数据是 8 位的字节,所以最后一块有时只剩下 2 位或 4 位。

举个例子:

>>> data = b'test'
>>> for byte in data:
...     print(format(byte, '08b'), end=" ")
...
01110100 01100101 01110011 01110100
>>>

如果你把这些二进制数据看作一个整数,那么这是如何转换成十进制和 Base-64 的(Base-64 表格):

base-2:  01 110100 011001 010111 001101 110100 (base-64 grouping shown)
base-10:                            1952805748
base-64:  B      0      Z      X      N      0

不过,base64 编码会这样重新分组这些数据:

base-2:  011101  000110  010101 110011 011101 00(0000) <- pad w/zeros to make a clean 6-bit chunk
base-10:     29       6      21     51     29      0
base-64:      d       G       V      z      d      A

所以,'B0ZXN0' 是我们二进制数据的 Base-64 版本,从数学角度来看。然而,base64 编码 需要反向进行编码(所以原始数据被转换成 'dGVzdA'),并且还有一个规则告诉其他应用程序最后剩下多少空间。这是通过在末尾填充 '=' 符号来实现的。因此,这个数据的 base64 编码是 'dGVzdA==',两个 '=' 符号表示在解码时需要从末尾去掉两个比特对,以使其与原始数据匹配。

让我们测试一下,看看我是不是在说谎:

>>> encoded = base64.b64encode(data)
>>> print(encoded)
b'dGVzdA=='

为什么要使用 base64 编码?

假设我需要通过电子邮件发送一些数据,比如这些数据:

>>> data = b'\x04\x6d\x73\x67\x08\x08\x08\x20\x20\x20'
>>> print(data.decode())
   
>>> print(data)
b'\x04msg\x08\x08\x08   '
>>>

我设置了两个问题:

  1. 如果我在 Unix 中尝试发送这封邮件,当 \x04 字符被读取时,邮件就会发送,因为这是 ASCII 中的 END-OF-TRANSMISSION(Ctrl-D),所以剩下的数据会被遗漏。
  2. 另外,虽然 Python 足够聪明,可以在我直接打印数据时转义所有的控制字符,但当那个字符串被解码为 ASCII 时,你会发现 'msg' 不见了。这是因为我用了三个 BACKSPACE 字符和三个 SPACE 字符来擦除 'msg'。因此,即使我没有 EOF 字符,最终用户也无法从屏幕上的文本转换回真实的原始数据。

这只是一个演示,展示了直接发送原始数据有多困难。把数据编码成 base64 格式可以确保你发送的数据是安全的,适合通过电子邮件等电子媒介发送。

354

base64编码是一种把8位的二进制数据转换成只使用特定字符的方式,这些字符包括A-Za-z0-9+/。这样做的目的是为了让数据能够通过一些不支持完整8位数据的渠道进行传输,比如电子邮件。

所以,它需要的是一串8位的字节。在Python 3中,你可以用b''的写法来创建这些字节。

如果你去掉b,它就变成了一个字符串。字符串是由Unicode字符组成的序列。base64对Unicode数据是无能为力的,因为Unicode不是8位的,实际上它并不算是任何位数的。:-)

在你的第二个例子中:

>>> encoded = base64.b64encode('data to be encoded')

所有的字符都可以很整齐地放进ASCII字符集里,因此base64编码在这种情况下其实是没什么意义的。你可以直接把它转换成ASCII,方法是:

>>> encoded = 'data to be encoded'.encode('ascii')

或者更简单的:

>>> encoded = b'data to be encoded'

在这种情况下,这两种方法是一样的。


* 大多数base64的变种在最后可能还会加一个=作为填充。此外,一些base64的变体可能会使用+/以外的字符。想了解更多,可以查看维基百科上的变种总结表

撰写回答