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


258

在此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

为什么是这样?


37
实际上,所有返回“ TypeError:预期的字节数,而不是str”的问题都具有相同的答案。
Lennart Regebro

Answers:


273

Base64编码需要8位二进制字节数据和编码它仅使用字符A-Za-z0-9+/*所以它可以在不保留任何数据的所有8位,例如电子邮件信道来传输。

因此,它需要一个8位字节的字符串。您可以使用以下b''语法在Python 3中创建它们。

如果删除b,它将成为一个字符串。字符串是Unicode字符序列。base64不知道如何处理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口味=的末尾也可能包含a 。此外,某些base64变体可能会使用+和以外的字符/。有关概述,请参见Wikipedia 的“ 变体”摘要表


174

简短答案

你需要一个推bytes-like对象(bytesbytearray,等)的base64.b64encode()方法。有两种方法:

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

或带有变量:

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

为什么?

在Python 3中,str对象不是C样式的字符数组(因此它们不是字节数组),而是对象,它们是没有任何固有编码的数据结构。您可以通过多种方式对该字符串进行编码(或解释)。最常见的(也是Python 3中的默认设置)是utf-8,特别是因为它与ASCII向后兼容(尽管使用最广泛的编码也是如此)。那就是当您采用a string并对其调用.encode()方法时发生的事情:Python正在以utf-8(默认编码)解释字符串,并为您提供与其对应的字节数组。

Python 3中的Base-64编码

最初,问题标题是关于Base-64编码的。继续阅读有关Base-64的内容。

base64编码采用6位二进制块,并使用字符AZ,az,0-9,'+','/'和'='进行编码(某些编码使用不同的字符代替“ +”和“ /”) 。这是基于基数64或基数64的数字系统的数学构造的字符编码,但它们有很大的不同。数学中的Base-64是一个数字系统,如二进制或十进制,您可以对整个数字进行基数的这种更改,或者(如果要转换的基数是2的乘方小于64,则从右到大)剩下。

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-10和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在读取字符后立即发送,因为END-OF-TRANSMISSION(Ctrl-D)为ASCII ,因此其余数据将不发送。
  2. 同样,虽然Python足够聪明,可以在我直接打印数据时转义我所有的邪恶控制字符,但是当该字符串被解码为ASCII时,您可以看到'msg'不存在。那是因为我使用了三个BACKSPACE字符和三个SPACE字符来擦除“ msg”。因此,即使我在EOF那里没有字符,最终用户也无法将屏幕上的文本转换为真实的原始数据。

这只是一个演示,向您展示简单发送原始数据有多困难。将数据编码为base64格式可为您提供完全相同的数据,但格式应确保可以安全地通过电子媒体(如电子邮件)发送。


6
base64.b64encode(s.encode()).decode()当您想要的只是一个字符串到字符串的转换时,它不是很好的Python语言。base64.encode(s)至少在python3中应该足够了。感谢您对python中的字符串和字节进行了很好的解释
MortenB

2
@MortenB是的,这很奇怪,但是从正面看,很清楚,只要工程师知道字节和字符串数组之间的差异,就会发生什么,因为像其他语言一样,它们之间没有一个映射(编码)承担。
格雷格·施密特

3
@MortenB顺便说一句,base64.encode(s)在Python3中不起作用;您是在说类似的东西吗?我认为这可能令人困惑的原因是,根据字符串的编码和内容,s可能没有1个唯一的表示形式(字节数组)。
格雷格·施密特

施密特:这只是一个简单例子。最常见的用例应该是这样。
MortenB

1
@MortenB,但是b64不仅用于文本,任何二进制内容都可以用b64编码(音频,图像等)。我认为,按照您的建议进行工作会更加隐藏文本和字节数组之间的差异,从而使调试更加困难。它只是将困难转移到其他地方。
Michael Ekoka

32

如果要编码的数据包含“外来”字符,我认为您必须使用“ UTF-8”进行编码

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

24

如果字符串是Unicode,最简单的方法是:

import base64                                                        

a = base64.b64encode(bytes(u'complex string: ñáéíóúÑ', "utf-8"))

# a: b'Y29tcGxleCBzdHJpbmc6IMOxw6HDqcOtw7PDusOR'

b = base64.b64decode(a).decode("utf-8", "ignore")                    

print(b)
# b :complex string: ñáéíóúÑ

当使用哪种编码来传输字符串很重要时,这实际上不是最简单的方法,而是最清晰的方法之一,它是通过base64进行数据传输的“协议”的一部分。
xuiqzy

12

您需要的一切:

expected bytes, not str

前导b使您的字符串成为二进制。

您使用什么版本的Python?2.x或3.x?

编辑:请参阅http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8位了解Python中字符串的详细信息3.x


谢谢,我正在使用3.x。为什么Python想要将其显式转换为二进制。Ruby中的相同之处是...需要>“ base64”,然后> Base64.encode64('data to be
encoding

2
@dublintech因为(unicode)文本与原始数据不同。如果要在Base64中对文本字符串进行编码,则首先需要确定字符编码(如UTF-8),然后使用字节而不是字符进行编码,可以以文本ascii安全格式进行编码。
fortran 2012年

2
这不能回答问题。他知道它适用于字节对象,但不适用于字符串对象。问题是为什么
Lennart Regebro 2012年

@fortran默认的Python3字符串编码是UTF,不知道,为什么必须显式设置它。
xmedeko

0

b只是意味着您将输入作为字节或字节数组而不是字符串。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.