为什么需要'b'来用Base64编码字符串?
根据这个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 个回答
如果你要编码的数据里包含一些“奇怪”的字符,我觉得你需要使用“UTF-8”编码。
encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
简短回答
你需要把一个类似于 bytes
的对象(比如 bytes
、bytearray
等)传给 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 '
>>>
我设置了两个问题:
- 如果我在 Unix 中尝试发送这封邮件,当
\x04
字符被读取时,邮件就会发送,因为这是 ASCII 中的END-OF-TRANSMISSION
(Ctrl-D),所以剩下的数据会被遗漏。 - 另外,虽然 Python 足够聪明,可以在我直接打印数据时转义所有的控制字符,但当那个字符串被解码为 ASCII 时,你会发现 'msg' 不见了。这是因为我用了三个
BACKSPACE
字符和三个SPACE
字符来擦除 'msg'。因此,即使我没有 EOF 字符,最终用户也无法从屏幕上的文本转换回真实的原始数据。
这只是一个演示,展示了直接发送原始数据有多困难。把数据编码成 base64 格式可以确保你发送的数据是安全的,适合通过电子邮件等电子媒介发送。
base64编码是一种把8位的二进制数据转换成只使用特定字符的方式,这些字符包括A-Z
、a-z
、0-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的变体可能会使用+
和/
以外的字符。想了解更多,可以查看维基百科上的变种总结表。