Pickle Cython 类

14

我需要保存和加载一个cython类实例。 我的cython类是这样的,还有几个方法:

import numpy as np
cimport numpy as np
cimport cython    
cdef class Perceptron_avg_my:
    cdef int wlen,freePos
    cdef np.ndarray w,wtot,wac,wtotc #np.ndarray[np.int32_t]
    cdef np.ndarray wmean  #np.ndarray[np.float32_t]    
    cdef public dict fpos    

    def __cinit__(self,np.int64_t wlen=4*10**7):
        self.fpos= dict()
        self.freePos=1
        self.wlen=wlen
        self.w=np.zeros(wlen,np.int32)
        self.wtot=np.zeros(wlen,np.int32)
        self.wac=np.zeros(wlen,np.int32)
        self.wtotc=np.zeros(wlen,np.int32)
        self.wmean=np.zeros(wlen,np.float32)

    cpdef evaluate_noavg(self,list f):
        cdef np.ndarray[np.int32_t] w = self.w
        cdef dict fpos = self.fpos        
        cdef bytes ff
        cdef int i
        cdef long int score=0

        for ff in f:
            i=fpos.get(ff,0)  
            if i != 0: 
                score += w[i]
        return score

我想使用cPickle模块。我知道我必须实现一个__reduce__(self)方法,但我遇到了一些问题,无法找到一个例子并且不太理解文档。

我尝试将以下内容添加到Perceptron_avg_my中,但并没有成功:

    def rebuild(self,l):
        self.fpos=l[0]
        self.freePos=l[1]

    def __reduce__(self):
        #print 'reduce call'
        return (Perceptron_avg_my.rebuild,(self.fpos,self.freePos))

有什么建议吗? 非常感谢!!!


这里有一个有用的讨论:https://ask.sagemath.org/question/8376/pickling-extension-classes/ - Yibo Yang
3个回答

17

我不知道你是否找到了,但官方Python文档有一个有关Pickling Extension Types的部分(不幸的是,Python 3没有这个文档的版本,但在Python 3中它的使用方式相同)。

我认为你有三个问题。首先,__reduce__返回的函数应该完全创建并返回一个新的对象,而你的rebuild函数只是设置了一些属性。其次,__reduce__返回的元组本身必须是可pickle的,而作为一个方法,Perceptron_avg_my.rebuild是不能pickle的(我认为这个问题预计将在Python 3.3或3.4中得到解决)。取而代之的是,你可以把它变成模块级别的函数。最后,参数(self.fpos,self.freePos)被逐个传递给rebuild - 你不必自己拆开元组。

下面的内容对我来说似乎有效(虽然你可能想存储其他属性的值,否则它们将仅具有由__init__设置的初始值):

#inside the class definition
def __reduce__(self):
    return (rebuild, (self.wlen, self.fpos, self.freePos))

#standalone function
def rebuild(wlen, fpos, freePos):
    p = Perceptron_avg_my(wlen)
    p.fpos = fpos
    p.freePos = freePos
    return p

1
谢谢James,如果我刚回复你的话很抱歉!!! 我认为还有另一个问题:__reduce__返回的函数不能在cython模块中(老实说我不明白为什么)。我会添加一个对我有效的解决方法...但我不知道是否有更好的解决方案。 - Francesco
很奇怪:当它在模块内被定义时,它对我有效。你不小心使用了 cdef 而不是 def 来定义函数,是吗?或者我们只是使用了不同版本的Cython或Python(我尝试过Python 3.2.3 / Cython 0.17和Python 2.7.2 / Cython 0.15.1)。 - James
我使用Python 2.7.3 / Cython 0.17。 - Francesco
如果我将Cython模块放在子包**./ml/**中,你的解决方案就不起作用了: cPickle.PicklingError: 无法pickle内置函数rebuild_perceptron:导入模块perceptron失败。我在main.py中使用from ml.perceptron import Perceptron。我的./ml/init.py是空的。我的子包有什么问题吗? - Francesco
非常好用,谢谢。但是文件本身不够简明扼要,涉及了许多边角情况,却没有一个简单的例子。 - dashesy

6
自 Cython 0.26(于2017年7月发布)起,实现pickle协议不再是必需的。所有不包含指针或联合的cdef类都可以自动进行pickling。对于包含结构体的类,默认情况下禁用自动pickling,这是由于存在高代码开销等原因。使用@cython.auto_pickle(True)修饰符可以为具有结构体的类启用自动pickling。
更多信息可在changelogStefan Behnel的网站中找到。

8
很高兴看到新版本发布了!不过我仍然需要实现自己的pickle机制,因为出现了“TypeError: no default reduce due to non-trivial __cinit__”的错误提示。 - Yibo Yang
1
那么包含指针的cdef类呢? - ibarrond
@ibarrond,最近我没有密切关注Cython的发展。我不知道这是否可行,或者你需要做什么才能使其工作。如果你找到了答案,请告诉我们 ;) - m00am

3

我使用了一个解决方法,它可行但不确定是否为最佳解决方案。

我创建了一个新的支持文件来声明由reduce调用的函数(如果我将其放在cython模块中,则无法起作用):

#perceptron_supp.py

from perceptron import Perceptron

def rebuild_perceptron(wlen,freePos,fpos,w,nw_avg,wtot_avg,wsup_avg,wmean_avg,wtot_my,wac_my,wtotc_my,wmean_my):
    return Perceptron(wlen,True,freePos,fpos,w,nw_avg,wtot_avg,wsup_avg,wmean_avg,wtot_my,wac_my,wtotc_my,wmean_my)

然后我在Cython模块中导入这个函数:
#perceptron.pyx

import numpy as np
cimport numpy as np
cimport cython

#added
from perceptron_supp import rebuild_perceptron

cdef class Perceptron:
    cdef int wlen,freePos
    cdef dict fpos

    cdef np.ndarray w #np.ndarray[np.int32_t]

    cdef int nw_avg
    cdef np.ndarray wtot_avg,wsup_avg #np.ndarray[np.int32_t]
    cdef np.ndarray wmean_avg  #np.ndarray[np.float64_t]

    cdef np.ndarray wtot_my,wac_my,wtotc_my #np.ndarray[np.int32_t]
    cdef np.ndarray wmean_my  #np.ndarray[np.float64_t]

    def __cinit__(self,int wlen=4*10**7,setValues=False,freePos=0,fpos=0,w=0,nw_avg=0,wtot_avg=0,wsup_avg=0,wmean_avg=0,wtot_my=0,wac_my=0,wtotc_my=0,wmean_my=0):
        if not setValues:            
            self.wlen=wlen
            self.freePos=1
            self.fpos= dict()

            self.w=np.zeros(wlen,np.int32)

            self.nw_avg=1
            self.wtot_avg=np.zeros(wlen,np.int32)            
            self.wsup_avg=np.zeros(wlen,np.int32)
            self.wmean_avg=np.zeros(wlen,np.float64)

            self.wtot_my=np.zeros(wlen,np.int32)    
            self.wac_my=np.zeros(wlen,np.int32)
            self.wtotc_my=np.zeros(wlen,np.int32)
            self.wmean_my=np.zeros(wlen,np.float64)
        else:           
            self.wlen=wlen
            self.freePos=freePos
            self.fpos=fpos

            self.w=w

            self.nw_avg=nw_avg
            self.wtot_avg=wtot_avg
            self.wsup_avg=wsup_avg
            self.wmean_avg=wmean_avg

            self.wtot_my=wtot_my
            self.wac_my=wac_my
            self.wtotc_my=wtotc_my
            self.wmean_my=wmean_my

    def __reduce__(self):
        return (rebuild_perceptron,(self.wlen,self.freePos,self.fpos,self.w,self.nw_avg,self.wtot_avg,self.wsup_avg,self.wmean_avg,self.wtot_my,self.wac_my,self.wtotc_my,self.wmean_my))

当我使用感知器模块时,我只需要执行以下操作:从perceptron中导入Perceptron,现在我可以在需要时执行cPickle.dump或cPickle.load。如果有更好的解决方案,请告诉我,非常感谢!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接