【问题标题】:Question about deserializing some numbers (bug??)关于反序列化一些数字的问题(错误??)
【发布时间】:2020-10-27 06:42:52
【问题描述】:

为了反序列化字节对象,我们使用pickle.loads()

import pickle
import numpy as np
pickle.loads(np.float64(0.34103))

并且预期的结果如下(因为 np.float64(0.34103) 不是字节对象,预计会出现适当的错误)

---------------------------------------------------------------------------
UnpicklingError Traceback (most recent call last)
<ipython-input-19-5c07606a60f1> in <module>
----> 1 pickle.loads(np.float64(0.34103))

UnpicklingError: invalid load key, '\xc1'.

这里我们有一些问题,像 0.34104 这样的数字(很少见)打印出以下结果而没有错误。

pickle.loads(np.float64(0.34104))
True

仅当转换后的字节以 b'\x88 开头时才会发生这种情况(例如 0.04263、0.08526、0.11651 ...)

np.float64(0.34104).tobytes()
b'\x88.\xa8o\x99\xd3\xd5?'

谁能回答这个问题是否是Python错误?

任何答案都将受到高度赞赏。

【问题讨论】:

  • tobytesnp.frombuffer 配对,并将pickle.dumpspickle.loads 配对。您尝试的配对没有意义。
  • 我在您的代码示例中没有看到 tobytes
  • 我的意思是“np.float64(0.34104).tobytes()”给了我们以 b'x88 开头的二进制表达式。此数字打印 True 而不是错误。谁能解释一下这种现象?
  • 你为什么要尝试pickle.loads(x),其中x 不是泡菜字节字符串?该协议并未设计用于接收此类随机数据。

标签: python numpy pickle


【解决方案1】:

pickle.loads 的输入并不完全限制为 bytes 对象。引用docs

返回对象的腌制表示数据的重构对象层次结构。 数据必须是bytes-like object

并引用“类似字节的对象”docs

支持Buffer Protocol 并可以导出 C 连续缓冲区的对象。

缓冲区协议是对象将其底层内存缓冲区暴露给其他代码的一种方式,对于有意义的对象。 NumPy 数组支持缓冲区协议,允许其他代码对数组的底层存储进行操作,而不是通过昂贵的 Python 级索引操作和包装对象。 NumPy 标量也支持缓冲区协议,以尽可能无缝地处理 0 维数据。


pickle.loads(np.float64(0.34103)) 的错误不是因为np.float64(0.34103) 不是字节。如果你试图传入一个普通的float,你会得到一个 TypeError,因为类型无效(floats 不是字节类),但是使用np.float64(0.34103),你得到的错误是因为@987654332 @尝试读取np.float64(0.34103)的缓冲区,发现里面的数据不是有效的pickle。

使用np.float64(0.34104),缓冲区的内容确实恰好是一个有效的pickle。侥幸的是,这些字节恰好匹配一个 NEWTRUE 操作码、一个 STOP 操作码和被忽略的尾随垃圾。 NEWTRUE 将 True 压入 pickle 堆栈,STOP 停止 pickle 执行,并返回 True。

【讨论】:

    【解决方案2】:

    这不是 Python 错误。 pickle.loads 可与任何 bytes-like object 一起使用,而 numpy 浮点数就是其中之一。
    从这开始,这就是字节的工作方式:字节表示没有类型的原始数据。代表 numpy float 0.34104 的 8 个字节恰好与代表 Python pickle True 的一些 8 个字节匹配,这纯属巧合。

    对于特定的 pickle 协议 2,b'\x88.\xa8o\x99\xd3\xd5?' 恰好是 True、End-Of-Pickle 和被忽略的尾随垃圾的操作码。

    >>> pickletools.dis(b'\x88.\xa8o\x99\xd3\xd5?')
        0: \x88 NEWTRUE
        1: .    STOP
    

    例如“numpy float 0.34104”的字节模式也匹配小端无符号/有符号Python整数4599815250385579656,大端无符号Python整数9812965835362522431,大端有符号Python整数-8633778238347029185, latin1 字符串'\x88.¨o\x99ÓÕ?' 和许多其他值。

    >>> int.from_bytes(np.float64(0.34103), byteorder='big', signed=False)
    13915064561919317311
    

    【讨论】:

    • 不是 Python 处理字节的一般方式。这里有特定的 NumPy 功能与pickle.loads 交互。大多数对象永远不会被这样解释。
    • 您声称这是因为“这就是字节的工作方式:字节是没有类型的原始数据。”,但 Python 不会将 bytes 视为“没有类型的原始数据”,它会不会将任意对象的内存表示重新解释为字节。
    • 如果您尝试将大多数对象传递给pickle.loads,它将立即引发 TypeError,而不是尝试进行任何类型的字节解释。您的答案立即跳转到关于泡菜协议和numpy.float64(0.34104) 内存布局的(错误)断言,甚至没有考虑为什么pickle.loads 将此对象视为有效输入。
    • 那个,以及pickle.loads 接受字节类对象的事实。我对这个问题的解释是,最重要的一点是为什么pickle 甚至接受不是bytes 实例的对象——这个问题表达了一种期望,即非bytes 输入应该引发错误。
    • 我很高兴我们能解决这个问题。 (错误的断言是断言字节匹配“代表 Python pickle True 的 8 个字节”,好像只有一个 pickle 可以解开到 True 并且这 8 个字节匹配那个 pickle。您的编辑也解决了这个问题。 )
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    相关资源
    最近更新 更多