酸洗numpy的阵列的子类时保存自定义属性酸洗numpy的阵列的子类时保存自定义属性(Preserv

2019-05-12 07:12发布

我创建了以下numpy的ndarray的子类的numpy的文档 。 具体地讲,我已添加了一个自定义属性通过修改所提供的代码。

我操纵这个类的实例并行循环中,使用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

谢谢。

Answer 1:

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'))

所以,我们得到了一个3元组回。 对于文档__reduce__描述一下每个元素做:

当返回一个元组,它必须是长二五行之间。 可选的元素可以被省略,或者可以无作为其值来提供。 该元组的内容被酸洗正常和用于重构在在unpickle时间的对象。 每个元素的语义如下:

  • 将被调用来创建对象的初始版本可调用对象。 该元组的下一个元素将为此可调用提供的参数,和后面的元素提供随后将被用于完全重构腌渍数据的附加状态信息。

    在在unpickle环境中,该对象必须是一个类,可调用注册为“安全构造”(见下文),或者必须有一个属性__safe_for_unpickling__具有真正的价值。 否则, UnpicklingError将在在unpickle环境得到提升。 需要注意的是像往常一样,可调用本身是由名腌制。

  • 的论据,可调用对象的元组。

  • 可选地,对象的状态,这将被传递给对象的__setstate__()如第酸洗描述并取储存正常类实例方法。 如果对象没有__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__ ,来设置我们的自定义属性,当我们unpickle。 我们只需要确保我们保留了所有的父类所需要的数据,并调用父的__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'


Answer 2:

我的dill (和pathos )的作者。 dill是酸洗numpy.arraynumpy能做到这一点本身。 @端午的解释是相当准确的。 我个人而言,我只是用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的叉multiprocessing (称为pathos.multiprocessing ),基本上唯一的变化是它采用dill代替cPickle ......,因此可以在一个序列化有很多的赫克更Pool.map 。 我认为,(目前),如果你想与你的一个子类的工作numpy.arraymultiprocessing (或pathos.multiprocessing ),你可能需要做一些像@dano建议-但不知道,因为我没想到的良好的情况下把我的头顶部,以测试你的子类。

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



文章来源: Preserve custom attributes when pickling subclass of numpy array