在Python 3中解码base64字符串(使用lxml或不使用)

12

我知道这看起来非常简单,但问题在于我对所有这些字节-字符串-Unicode(以及编码-解码)的东西还没有清晰的理解。

我一直在尝试让我的代码在Python 3上运行。我卡住的部分是当我使用解析XML并解码其中包含的一个base64字符串时。

现在代码的运行方式如下:

我使用XPath查询'.../binary/text()'检索二进制数据。这会产生一个包含一个对象的单元素列表。然后,在Python 2中,我能够执行以下操作:

decoded = source.decode('base64')

最后

output = numpy.frombuffer(decoded)

然而,在Python 3上,我收到了一个错误消息,提示说

AttributeError: 'lxml.etree._ElementUnicodeResult' object has no attribute 'decode'

这并不奇怪,因为lxml.etree._ElementUnicodeResultstr的一个子类。

另一种方法是通过使用以下方法获取包含相同数据的真实str

 binary = tree.xpath('//binary')[0]
 binary_string = binary.text

基本上就是一样的。那么我该怎么解码base64呢?我看过base64模块,但它需要一个bytes对象作为参数,我不知道如何将str表示为bytes,因为如果我尝试构造一个bytes对象,Python会尝试编码字符串,而我不需要这样做。

通过Google进一步搜索,我找到了binascii模块(如果我没记错的话,它间接地从base64调用),但在我的字符串上调用binascii.b2a_base64()会产生以下结果:

TypeError: 'str' does not support the buffer interface

顺便提一句,我甚至在如何在Python 3中解码十六进制字符串上找到了一个已回答的问题,但是这是使用专用方法bytes.fromhex()完成的,所以我不知道它是否有帮助。

请问有人能告诉我我错过了什么吗?恐怕大部分帖子都与我的问题无关,只会让我更感到羞愧,但至少你们知道我尝试了什么


4
顺便提一下,Ned Batchelder有一个非常棒的演讲关于bytes-str-unicode相关的问题:《实用Unicode,或:我如何停止痛苦?》(Pragmatic Unicode, or: How Do I Stop the Pain?)。链接在这里:http://nedbatchelder.com/text/unipain.html。 - user395760
感谢 @delnan,我已经完成了一半,已经帮了我很多了 :) - Lev Levitsky
2个回答

13

好的,我想我要总结一下我目前对事物的理解(请随意纠正我)。希望它能帮助其他像我一样困惑的人。

当然,所有功劳都归thebjorndelnan

所以,从最常见的东西开始: 有Unicode,它是一个全球标准,为您可以想象到的所有奇特字符分配代码(或代码点)。这些代码只是整数。根据维基百科,截至Unicode 6.1,有109,975个图形字符。

然后有编码,它定义了如何用字节码指定Unicode字符。一个字节不足以指定任意Unicode字符。虽然,如果你只取其中的一小部分(英文字母、数字、标点符号、一些控制字符),你可以用每个字符一个字节(甚至7位;请参见ASCII)。


要在任何地方传递Unicode字符串,需要将其编码为字节,然后可以在另一端进行解码。
在Python 2中,str实际上是字节,而unicode是Unicode,但当需要时,Python 2会为您执行隐式编码/解码。它会尝试使用ASCII编码。
在Python 3中,str始终是Unicode字符串,而bytes是实际字节的新数据类型。Python 3永远不会执行隐式转换,您总是需要自己做并指定编码方式。这意味着,在理解发生了什么之前,您的程序将无法正常工作,这完全发生在我身上了。
现在,大致清楚了,让我们继续谈论编码问题-base64编码,它也是一种编码方式,但有着稍微不同的含义。假设你有一些二进制数据(即字节),可能代表任何内容(在我的情况下是一堆float)。现在你想用一个字符串来表示这个二进制数组。这就是base64编码的意思:你把字节表示为ASCII字符串。
Base64意味着6位,因此在base64编码的字符串中,一个字符代表6位数据。这就是为什么base64编码的字符串需要长度是4的倍数:否则编码的字节数将不是整数。
最后,要解码base64,您需要一个ASCII字符串。Unicode字符串不行,只能有来自base64字母表的字符。在Python中,Base64模块可以完成这项工作。 base64.b64decode()函数以字节字符串作为参数。在Python 2中,它表示:str。在Python 3中,它表示:bytes。因此,如果您有一个str,例如
>>> s = 'U3RhY2sgT3ZlcmZsb3c='

在Python 2中,你可以这样做。
>>> s.decode('base64')

因为s已经是ASCII码了。在Python 3中,你需要先将其编码为ASCII码,所以你需要执行以下操作:

>>> base64.b64decode(s.encode('ascii'))

顺便提一下,这将返回一个 bytes 对象,因此你可以自行决定如何处理这些字节。也许是我的浮点数,但也许你应该尝试将其解码为 ASCII :) 然而在 Python 2 中它将只是一个 str。无论如何,请查看 struct 来获取从这些字节中解包数据的工具。
因此,如果你需要代码在 Python 2 和 3 上都能运行,请使用最后一个选项。如果你要从 base64 解码文本以确保最终得到 Unicode,请对其进行解码:
>>> base64.b64decode(s.encode('ascii')).decode('ascii')

在Python 2中,encode('ascii')不会有效地执行任何操作,因为它应用于str。因此,它将首先对Unicode进行隐式转换,然后执行您想要的操作(将其转换回ASCII)。decode('ascii')将在Python 2上返回一个unicode对象。

非常好的总结 :-) 如果您想保存一个浮点数列表,也许pickle模块比struct模块更容易使用?可以尝试类似这样的代码:base64.b64encode(pickle.dumps([2.718, 3.141])) - thebjorn
@thebjorn 谢谢:) 我实际上正在使用 numpy.frombuffer(),我只是提到了 struct 作为参考,以便考虑一般情况。 - Lev Levitsky
除非你理解了程序的运行原理,否则你的程序是无法正常工作的。这通常是一件好事情。 :) - AKX
确实,并且也没有花太多时间。 - Lev Levitsky

2

我没有安装Python 3,但听起来你需要将从lxml返回的Unicode转换为字节,可能需要调用.encode('ascii')方法?


天啊...我知道这很简单。只是我无法将这些东西在我的脑海中安排得井井有条。我一直把我的字符串看作是一种编码,所以我真的没有想到我需要对它进行编码才能得到bytes。谢谢。 - Lev Levitsky
4
把Unicode想象成普通的字符串,需要在发送到"硬件"之前进行编码,在接收到"硬件"后进行解码。 :-) - thebjorn
我觉得这样一个长问题需要一个更长的答案,但无论如何,非常感谢您指出了正确的方向 :) - Lev Levitsky

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