24

我在numpy 文档之后创建了一个 numpy ndarray 的子类。特别是,我通过修改提供的代码添加了自定义属性。

我正在使用 Python 在并行循环中操作此类的实例multiprocessing。据我了解,范围本质上“复制”到多个线程的方式是使用pickle.

我现在遇到的问题与 numpy 数组的腌制方式有关。我找不到任何关于此的全面文档,但莳萝开发人员之间的一些讨论表明我应该专注于该__reduce__方法,该方法被称为酸洗。

任何人都可以对此有所了解吗?最小的工作示例实际上只是我上面链接到的 numpy 示例代码,为了完整起见,复制到这里:

import numpy as np

class RealisticInfoArray(np.ndarray):

    def __new__(cls, input_array, info=None):
        # Input array is an already formed ndarray instance
        # We first cast to be our class type
        obj = np.asarray(input_array).view(cls)
        # add the new attribute to the created instance
        obj.info = info
        # Finally, we must return the newly created object:
        return obj

    def __array_finalize__(self, obj):
        # see InfoArray.__array_finalize__ for comments
        if obj is None: return
        self.info = getattr(obj, 'info', None)

现在问题来了:

import pickle

obj = RealisticInfoArray([1, 2, 3], info='foo')
print obj.info  # 'foo'

pickle_str = pickle.dumps(obj)
new_obj = pickle.loads(pickle_str)
print new_obj.info  #  raises AttributeError

谢谢。

4

3 回答 3

29

np.ndarray用来__reduce__腌制自己。当您调用该函数以了解发生了什么时,我们可以看看它实际返回的内容:

>>> obj = RealisticInfoArray([1, 2, 3], info='foo')
>>> obj.__reduce__()
(<built-in function _reconstruct>, (<class 'pick.RealisticInfoArray'>, (0,), 'b'), (1, (3,), dtype('int64'), False, '\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'))

所以,我们得到了一个三元组。__reduce__描述每个元素在做什么的文档:

返回元组时,它的长度必须在 2 到 5 个元素之间。可选元素可以省略,也可以提供 None 作为它们的值。这个元组的内容被正常腌制,并用于在解腌时重建对象。每个元素的语义是:

  • 将被调用以创建对象的初始版本的可调用对象。元组的下一个元素将为这个可调用对象提供参数,后面的元素提供额外的状态信息,这些信息随后将用于完全重建腌制数据。

    在 unpickling 环境中,该对象必须是一个类,一个注册为“安全构造函数”(见下文)的可调用对象,或者它必须具有一个__safe_for_unpickling__具有真值的属性。否则,UnpicklingError将在 unpickling 环境中引发 an 。请注意,像往常一样,可调用对象本身是按名称腌制的。

  • 可调用对象的参数元组。

  • 可选地,对象的状态,将被传递给对象的 __setstate__()方法,如 Pickling 和 unpickling 普通类实例部分所述。如果对象没有__setstate__()方法,那么和上面一样,该值必须是一个字典,它将被添加到对象的__dict__.

所以,_reconstruct是调用来重建对象的函数,(<class 'pick.RealisticInfoArray'>, (0,), 'b')是传递给该函数的参数,(1, (3,), dtype('int64'), False, '\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'))并被传递给类' __setstate__。这给了我们一个机会;我们可以覆盖__reduce__并提供我们自己的元组到__setstate__,然后另外覆盖__setstate__,以在我们取消腌制时设置我们的自定义属性。我们只需要确保我们保留了父类需要的所有数据,并且也调用了父类__setstate__

class RealisticInfoArray(np.ndarray):
    def __new__(cls, input_array, info=None):
        obj = np.asarray(input_array).view(cls)
        obj.info = info
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self.info = getattr(obj, 'info', None)

    def __reduce__(self):
        # Get the parent's __reduce__ tuple
        pickled_state = super(RealisticInfoArray, self).__reduce__()
        # Create our own tuple to pass to __setstate__
        new_state = pickled_state[2] + (self.info,)
        # Return a tuple that replaces the parent's __setstate__ tuple with our own
        return (pickled_state[0], pickled_state[1], new_state)

    def __setstate__(self, state):
        self.info = state[-1]  # Set the info attribute
        # Call the parent's __setstate__ with the other tuple elements.
        super(RealisticInfoArray, self).__setstate__(state[0:-1])

用法:

>>> obj = pick.RealisticInfoArray([1, 2, 3], info='foo')
>>> pickle_str = pickle.dumps(obj)
>>> pickle_str
"cnumpy.core.multiarray\n_reconstruct\np0\n(cpick\nRealisticInfoArray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I3\ntp6\ncnumpy\ndtype\np7\n(S'i8'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'<'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\np13\nS'foo'\np14\ntp15\nb."
>>> new_obj = pickle.loads(pickle_str)
>>> new_obj.info
'foo'
于 2014-10-28T01:08:02.703 回答
8

我是dill(和pathos)作者。 dill之前腌制numpy.arraynumpy可以自己做。@dano 的解释非常准确。就我个人而言,我只是使用dill它并让它为您完成工作。使用dill,您不需要__reduce__,因为dill它有几种获取子类属性的方法……其中一种是__dict__为任何类对象存储 。 pickle不这样做,b / c它通常通过名称引用与类一起使用,而不是存储类对象本身......所以你必须为__reduce__pickle工作。在大多数情况下,不需要使用dill.

>>> import numpy as np
>>> 
>>> class RealisticInfoArray(np.ndarray):
...     def __new__(cls, input_array, info=None):
...         # Input array is an already formed ndarray instance
...         # We first cast to be our class type
...         obj = np.asarray(input_array).view(cls)
...         # add the new attribute to the created instance
...         obj.info = info
...         # Finally, we must return the newly created object:
...         return obj
...     def __array_finalize__(self, obj):
...         # see InfoArray.__array_finalize__ for comments
...         if obj is None: return
...         self.info = getattr(obj, 'info', None)
... 
>>> import dill as pickle
>>> obj = RealisticInfoArray([1, 2, 3], info='foo')
>>> print obj.info  # 'foo'
foo
>>> 
>>> pickle_str = pickle.dumps(obj)
>>> new_obj = pickle.loads(pickle_str)
>>> print new_obj.info
foo

dill可以将自己扩展到pickle(基本上通过copy_reg它知道的所有内容),因此您可以dill在任何使用pickle. 现在,如果你要使用multiprocessing,你有点搞砸了,因为它使用cPickle. 然而,有(称为) 的pathos分支,基本上唯一的变化是它使用而不是……,因此可以在 a 中序列化更多的东西。我认为(目前)如果你想使用你的 a in (或)的子类,你可能需要做类似@dano 建议的事情——但不确定,因为我没有想到一个好的案例我的头来测试你的子类。multiprocessingpathos.multiprocessingdillcPicklePool.mapnumpy.arraymultiprocessingpathos.multiprocessing

如果您有兴趣,请到pathos这里:https ://github.com/uqfoundation

于 2014-10-28T08:09:07.163 回答
1

这是对@dano 的回答和@Gabriel 的评论的轻微改进。即使使用子类,利用__dict__属性进行序列化也对我有用。

def __reduce__(self):
    # Get the parent's __reduce__ tuple
    pickled_state = super(RealisticInfoArray, self).__reduce__()
    # Create our own tuple to pass to __setstate__, but append the __dict__ rather than individual members.
    new_state = pickled_state[2] + (self.__dict__,)
    # Return a tuple that replaces the parent's __setstate__ tuple with our own
    return (pickled_state[0], pickled_state[1], new_state)

def __setstate__(self, state):
    self.__dict__.update(state[-1])  # Update the internal dict from state
    # Call the parent's __setstate__ with the other tuple elements.
    super(RealisticInfoArray, self).__setstate__(state[0:-1])

这是一个完整的例子:https ://onlinegdb.com/SJ88d5DLB

于 2019-09-12T10:07:20.400 回答