我已经根据numpy文档创建了numpy ndarray的子类。特别是,我通过修改提供的代码添加了自定义属性。
我正在使用Python在并行循环中处理此类的实例multiprocessing。据我了解,将范围本质上“复制”到多个线程的方法是使用pickle。
multiprocessing
pickle
我现在要面对的问题与numpy数组的腌制方式有关。我找不到关于此的任何综合文档,但是莳萝开发人员之间的一些讨论建议我应该专注于该__reduce__方法,该方法在腌制时被调用。
__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
谢谢。
np.ndarray用于__reduce__腌制自己。我们可以看一下调用该函数时实际返回的信息,以了解发生了什么:
np.ndarray
>>> 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作为其值。将该元组的内容照常进行腌制,并在去腌制时用于重建对象。每个元素的语义是: * 将被调用以创建对象的初始版本的可调用对象。元组的下一个元素将为此可调用对象提供参数,而后一个元素将提供其他状态信息,这些信息随后将用于完全重建腌制的数据。 在解开环境中,该对象必须是一个类,注册为“安全构造函数”的可调用对象(请参见下文),或者它必须具有一个__safe_for_unpickling__值为true的属性。否则,UnpicklingError将在非酸洗环境中引发。注意,像往常一样,可调用对象本身是按名称腌制的。 可调用对象的参数元组。 (可选)对象的状态,该状态将传递给对象的 __setstate__()方法,如“腌制和取消腌制普通类实例”一节中所述。如果对象没有__setstate__()方法,则如上所述,该值必须是字典,并将其添加到对象的__dict__。
返回一个元组时,它的长度必须在2到5个元素之间。可选元素可以省略,也可以提供None作为其值。将该元组的内容照常进行腌制,并在去腌制时用于重建对象。每个元素的语义是:
* 将被调用以创建对象的初始版本的可调用对象。元组的下一个元素将为此可调用对象提供参数,而后一个元素将提供其他状态信息,这些信息随后将用于完全重建腌制的数据。
在解开环境中,该对象必须是一个类,注册为“安全构造函数”的可调用对象(请参见下文),或者它必须具有一个__safe_for_unpickling__值为true的属性。否则,UnpicklingError将在非酸洗环境中引发。注意,像往常一样,可调用对象本身是按名称腌制的。
__safe_for_unpickling__
UnpicklingError
可调用对象的参数元组。
(可选)对象的状态,该状态将传递给对象的 __setstate__()方法,如“腌制和取消腌制普通类实例”一节中所述。如果对象没有__setstate__()方法,则如上所述,该值必须是字典,并将其添加到对象的__dict__。
__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__,即可:
_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__
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'