使用Python 2.7对Unicode字符串进行Base64编码

9

我从一个webservice中使用requests模块获取了一个Unicode字符串,其中包含二进制文档的字节(例如PCL)。其中一个字节的值为248,尝试对其进行base64编码会导致以下错误:

In [68]: base64.b64encode(response_dict['content']+'\n')
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
C:\...\<ipython-input-68-8c1f1913eb52> in <module>()
----> 1 base64.b64encode(response_dict['content']+'\n')

C:\Python27\Lib\base64.pyc in b64encode(s, altchars)
     51     """
     52     # Strip off the trailing newline
---> 53     encoded = binascii.b2a_base64(s)[:-1]
     54     if altchars is not None:
     55         return _translate(encoded, {'+': altchars[0], '/': altchars[1]})

UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 272: ordinal not in range(128)

In [69]: response_dict['content'].encode('base64')
---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
C:\...\<ipython-input-69-7fd349f35f04> in <module>()
----> 1 response_dict['content'].encode('base64')

C:\...\base64_codec.pyc in base64_encode(input, errors)
     22     """
     23     assert errors == 'strict'
---> 24     output = base64.encodestring(input)
     25     return (output, len(input))
     26

C:\Python27\Lib\base64.pyc in encodestring(s)
    313     for i in range(0, len(s), MAXBINSIZE):
    314         chunk = s[i : i + MAXBINSIZE]
--> 315         pieces.append(binascii.b2a_base64(chunk))
    316     return "".join(pieces)
    317

UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 44: ordinal not in range(128)

我觉得这有点令人惊讶,因为248在无符号字节范围内(可以保存在字节字符串中),但我的真正问题是:编码这个字符串的最佳或正确方法是什么? 我目前的解决方法是:
In [74]: byte_string = ''.join(map(compose(chr, ord), response_dict['content']))

In [75]: byte_string[272]
Out[75]: '\xf8'

这似乎可以正常工作,生成的byte_string能够进行base64编码,但是似乎应该有更好的方法。是否有其他方法?


1
248 可能在无符号字节的范围内,但它不在标准 ASCII [0-127] 的范围内。 - Cameron
@Cameron:确实是一个好的观点,但这仍然无法解释问题,因为当相同的值在字节字符串中时,不会导致该错误。 - Marcin
看我的回答:-) 你所做的是获取unicode字符串的代码点并将其视为字节。这最好是可疑的,因为不能保证代码点甚至在0-255范围内。更糟糕的是,之后没有人会知道如何解释这个字节字符串,因为它是以自定义的未定义编码形式存在的。 - Cameron
1
@Cameron:重申一下,这些数据不是字符代码点,而是二进制数据。 - Marcin
5个回答

18

您有一个 unicode 字符串需要进行 base64 编码。问题在于,b64encode() 只能作用于 bytes 而非 characters。因此,您需要将您的 unicode 字符串(即抽象 Unicode 代码点序列)转换为字节字符串。

将抽象 Unicode 字符串映射为具体的一系列字节的过程称为编码。Python 支持多种编码方式;我建议您使用广泛应用的 UTF-8 编码:

byte_string = response_dict['content'].encode('utf-8')

请注意,解码字节的人也需要知道使用了哪种编码方式,以便通过相应的 decode() 函数获取回一个 unicode 字符串:

# Decode
decoded = byte_string.decode('utf-8')

了解更多有关Unicode和编码的好起点是Python文档和Joel Spolsky的这篇文章


1
要明确一点:我的Unicode字符串的内容是二进制数据。我不能将它们更改为其他字节。是否有一个身份编码? - Marcin
2
@Marcin:你不能拥有一个包含二进制数据的unicode字符串。这是自相矛盾的!如果unicode字符串的字节应该表示二进制数据(似乎在这里是这种情况),那么它不应该被存储在unicode对象中,因为它根本不是Unicode! - Cameron
为什么不添加BOM?实际上,这个特性有助于检测字符串是否为UTF-8编码。 - sebix
@sebix:我认为最好只在文件开头使用BOM;在任何地方检查字符串是否有BOM的开销和复杂性似乎太高了。不过,我的编码混淆了,-sig确实会添加BOM。 - Cameron

5
我建议先将其编码为UTF-8之类的内容,然后再进行base64编码:
In [12]: my_unicode = u'\xf8'

In [13]: my_utf8 = my_unicode.encode('utf-8')

In [15]: base64.b64encode(my_utf8)
Out[15]: 'w7g='

将编码转换为UTF-8没有意义。你要么从UTF-8编码到字节/ASCII,要么从ASCII解码到UTF-8。这是相反的过程。 - sebix

3

由于您在处理二进制数据,所以我不确定使用utf-8编码是否是一个好主意。我猜这取决于您打算如何使用base64编码表示。如果您能将数据作为字节字符串而不是Unicode字符串检索,则可能更好。我从未使用过requests库,但浏览文档表明这是可能的。有关“二进制响应内容”和“原始响应内容”的部分正在讨论。


谢谢!结果证明,使用Latin-1编码产生的字节序列与我的解决方法完全相同。 - Marcin
1
@Marcin:你需要确保请求模块没有假定你正在处理文本,应用了默认编码,并将你的二进制数据解码为Unicode。如果是这种情况,你就会遇到麻烦。你能验证一下内容是否符合你的预期吗? - Dan Gerhardsson
2
在更加仔细地阅读文档后,我发现 requests 还告诉我用于将响应解码为 Unicode 的编码方式,因此我可以始终可靠地使用该编码重新编码(这样可以再次产生相同的字节)。 - Marcin

1

应该可以将响应作为二进制字节获取,完全跳过解码和编码步骤。 requests 总是有可能选择一种编码方式,在往返中丢失一些数据或出现错误。

文档的这部分 "二进制响应内容" 似乎非常适合您的问题。


0
如果是二进制数据...为什么还要进行编码/解码呢?特别是"base64.encodestring"部分。下面是我将图像编码为base64的方法,直接添加到我的Python代码中,而不需要额外的文件。顺便提一下,我使用的是2.7.2版本。
import base64
iconfile = open("blah.icon","rb")
icondata = iconfile.read()
icondata = base64.b64encode(icondata)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接