【问题标题】:Why do I need 'b' to encode a string with Base64?为什么我需要 'b' 来使用 Base64 对字符串进行编码?
【发布时间】:2012-02-13 01:31:12
【问题描述】:

按照python example,我将字符串编码为 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

这是为什么?

【问题讨论】:

  • 实际上所有返回“TypeError: expected bytes, not str”的问题都有相同的答案。
  • 那个 b 仅仅意味着您将输入作为字节或字节数组而不是字符串。

标签: python python-3.x base64


【解决方案1】:

base64 编码采用 8 位二进制字节数据并仅使用字符 A-Za-z0-9+/* 对其进行编码,因此它可以通过不保留的通道传输所有 8 位数据,例如电子邮件。

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

如果你删除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 风格还可能在末尾包含 = 作为填充。此外,某些 base64 变体可能使用+/ 以外的字符。请参阅 Wikipedia 上的 Variants summary table 了解概览。

【讨论】:

  • “它需要一个 8 位字节的字符串”。计算机中的一个字节由 8 位组成,所有编程语言(包括 Python str)中的大多数数据类型都是由字节组成的,所以我不明白你的意思。也许“它想要一个 8 位字符的字符串”,作为 ASCII 字符串?
  • @AlanEvangelista 从概念上讲,Python 字符串是 Unicode 字符序列。它不需要任何特定的底层二进制表示。另一方面,bytesbytearray 对象实际上确实表示字节/八位字节序列。 (尽管它也不需要任何特定的底层二进制表示。)
【解决方案2】:

简答

您需要将bytes-like 对象(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 位二进制块并使用字符 AZ、az、0-9、'+'、'/' 和 '=' 对它们进行编码(某些编码使用不同的字符代替 '+ ' 和 '/')。这是一种基于 radix-64 或 base-64 数字系统的数学结构的字符编码,但它们非常不同。数学中的 Base-64 是一个类似于二进制或十进制的数字系统,您可以对整个数字进行这种基数更改,或者(如果您要转换的基数是 2 小于 64 的幂)从右到离开了。

base64编码中,翻译是从左到右进行的;前 64 个字符就是为什么它被称为 base64 encoding。第 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 (table for 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 encoding 将重新分组这些数据:

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 encoding 必须以相反的方向进行编码(因此原始数据转换为 '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 字符来擦除“味精”。因此,即使我没有 EOF 字符,最终用户也无法将屏幕上的文本转换为真实的原始数据。

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

【讨论】:

  • base64.b64encode(s.encode()).decode() 当你想要的只是一个字符串到字符串的转换时,它不是很pythonic。 base64.encode(s) 至少在 python3 中应该足够了。感谢您对python中的字符串和字节进行了很好的解释
  • @MortenB 是的,这很奇怪,但从好的方面来说,只要工程师意识到字节数组和字符串数组之间的区别,情况就很清楚了,因为没有单一的映射 (编码)在它们之间,正如其他语言所假设的那样。
  • @MortenB 顺便说一句,base64.encode(s) 在 Python3 中不起作用;你是说应该有这样的东西吗?我认为它可能令人困惑的原因是,根据字符串的编码和内容,s 可能没有 1 个作为字节数组的唯一表示。
  • Schmitt:这只是一个简单的例子。最常见的用例应该是这样的。
  • @MortenB 但 b64 不仅仅用于文本,任何二进制内容都可以进行 b64 编码(音频、图像等)。在我看来,让它按照您的建议工作会更加隐藏文本和字节数组之间的差异,从而使调试更加困难。它只是将难度转移到其他地方。
【解决方案3】:

如果要编码的数据包含“异国”字符,我认为你必须用“UTF-8”编码

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

【讨论】:

    【解决方案4】:

    如果字符串是 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传输数据的“协议”的一部分。
    【解决方案5】:

    这里有你所需要的:

    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-bit 了解 Python 3.x 中字符串的血腥细节

    【讨论】:

    • 感谢我正在使用 3.x。为什么 Python 要将其显式转换为二进制。在 Ruby 中也是这样...需要 > "base64" 然后 > Base64.encode64('待编码的数据')
    • @dublintech 因为(unicode)文本与原始数据不同。如果你想用 Base64 编码一个文本字符串,首先你需要确定字符编码(如 UTF-8),然后你有字节而不是字符,你可以以文本 ascii 安全的形式编码。
    • 这不能回答问题。他知道它适用于字节对象,但不适用于字符串对象。问题是为什么
    • @fortran 默认Python3字符串编码是UTF,不知道为什么要显式设置。
    猜你喜欢
    • 1970-01-01
    • 2016-01-27
    • 2011-01-10
    • 2014-07-14
    • 1970-01-01
    • 1970-01-01
    • 2017-11-30
    • 2019-07-11
    • 2018-11-10
    相关资源
    最近更新 更多