将 pickle-able 属性添加到 numpy.ndarray 的子类

Adding a pickle-able attribute to a subclass of numpy.ndarray

我想将 属性 (.csys) 添加到 numpy.ndarray 的子类中:

import numpy as np

class Point(np.ndarray):

    def __new__(cls, arr, csys=None):
        obj = np.asarray(arr, dtype=np.float64).view(cls)
        obj._csys = csys
        return obj

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

    @property
    def csys(self):
        print('Getting .csys')
        return self._csys

    @csys.setter
    def csys(self, csys):
        print('Setting .csys')
        self._csys = csys

然而,当我运行这个测试代码时:

pt = Point([1, 2, 3])
pt.csys = 'cmm'
print("pt.csys:", pt.csys)

# Pickle, un-pickle, and check again
import pickle

pklstr = pickle.dumps(pt)
ppt = pickle.loads(pklstr)

print("ppt.csys:", ppt.csys)

似乎无法 pickle 属性:

Setting .csys
Getting .csys
pt.csys: cmm
Getting .csys
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
C:\Rut\Vanes\bin\pointtest.py in <module>()
     39     ppt = pickle.loads(pklstr)
     40 
---> 41     print("ppt.csys:", ppt.csys)

C:\Rut\Vanes\bin\point.py in csys(self)
     15     def csys(self):
     16         print('Getting .csys')
---> 17         return self._csys
     18 
     19     @csys.setter

AttributeError: 'Point' object has no attribute '_csys'

我尝试在不使用装饰器的情况下做同样的事情(例如定义 get_csys()set_csys(),加上 csys = property(__get_csys, __set_csys),但结果相同。

我在 Python 3.6.3

下使用 numpy 1.13.3

这个问题已经有人提出并回答了here。简而言之,numpy 使用 __reduce____setstage__ 来 pickle 自身。适应上述情况的覆盖如下所示:

def __reduce__(self):
    # Get the parent's __reduce__ tuple
    pickled_state = super().__reduce__()
    # Create our own tuple to pass to __setstate__
    new_state = pickled_state[2] + (self._csys,)
    # 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._csys = state[-1]  # Set the _csys attribute
    # Call the parent's __setstate__ with the other tuple elements.
    super().__setstate__(state[0:-1])

另请注意,在这种简单情况下,getter 和 setter 方法(分别在 @property@csys.getter 装饰器下)并不是严格必需的。如果它们被免除,直接访问 .csys,而不是通过 'private' ._csys 属性。