当前位置: 首页 > 工具软件 > Pickle > 使用案例 >

pickle.PicklingError错误 (pickle 无法保存 namedtuple)

罗安宁
2023-12-01

问题:pickle 无法保存 namedtuple

具体描述:

  • 报错信息

Traceback (most recent call last):

  File "/home/liyd/anaconda3/envs/py3/lib/python3.6/site-packages/numpy/lib/npyio.py", line 529, in save

    pickle_kwargs=dict(fix_imports=fix_imports))

  File "/home/liyd/anaconda3/envs/py3/lib/python3.6/site-packages/numpy/lib/format.py", line 664, in write_array

    pickle.dump(array, fp, protocol=3, **pickle_kwargs)

_pickle.PicklingError: Can't pickle <class '__main__.cam'>: attribute lookup cam on __main__ failed

python-BaseException

查看上述报错信息,定位到错误出现在一个叫做__main__.cam的类里,pickle是在__main__中调用的,所以尝试在__main__中寻找cam,这不奇怪,看一下pickle调用的位置:

  • 追根溯源

for sequence in seqList:

    audioDataDic= {}

    for cam_number in range(1, 4):

        DATA, CFGforGCF = InitGCF(datasetPath_au, sequence, cam_number)

        audioData = {f'{sequence}_cam{cam_number}': DATA}

        audioDataDic.update(audioData)

    # save the imgDataList as {sequence}_sampleList.npz

    folderPath = f'{datasetPath_save}/audio/{sequence}'

    if not os.path.exists(folderPath):

        os.makedirs(folderPath)

    np.save(f'{folderPath}/{sequence}_audio.npz', audioDataDic)# audioDic=audioDataDic

    print(f'save audio.npz for {sequence}')

问题不在pickle,而在cam,为什么main中找不到cam呢?因为cam类是定义在函数里的,出了函数类,cam的定义就消失了,cam成了一个孤儿对象,pickle当然也找不到cam的定义。看一下cam的定义部分:

  • 孤儿对象无法保存

class DataMain:

    def __init__(self,cfgGCF):

        ……



    def loadCamAlign(self,datasetPath):

        ……

        cam = {

            'Pmat': np.concatenate(([data[0][0]], [data[1][0]], [data[2][0]]), axis=0),

            'K': np.concatenate(([data[0][1]], [data[1][1]], [data[2][1]]), axis=0),

            'alpha_c': np.concatenate(([data[0][2]], [data[1][2]], [data[2][2]]), axis=0),

            'kc': np.concatenate(([data[0][3]], [data[1][3]], [data[2][3]]), axis=0),

        }



        self.cam = namedtuple('cam',cam.keys())(**cam)

        dataPath = f'{datasetPath}/rigid010203.mat'

        data = scio.loadmat(dataPath)['rigid'][0]

        self.align_mat = data[0][1]

cam的定义实际上在DataMain.loadCamAlign.cam里且没有引用名,pickle找的时候只知道去__main__.cam里找,自然是找不到的。那么怎么才能找到呢?答案就是把类的定义放在外面。

解决:

  • 定义cam类

分析一下定义self.cam = namedtuple('cam',cam.keys())(**cam)这句话:

namedtuple('cam',cam.keys())

首先,namedtuple是一个类型工厂,输入类型的名字和参数,可以输出一个新的类。这个新的类目前没有引用指向它。想让pickle查到它,就要给它一个名字,并且放在函数外面,避免退出函数的时候类定义被销毁,参考1中说明了这个原理。

(**cam)

其次,采用这个“匿名类”新建了一个对象,其参数由字典cam决定。最后,把这个单例对象返回给self.cam。

  • 重新定义cam类

camCls = namedtuple('cam',['Pmat','K','alpha_c','kc'])

camCls此时存储了这个类的索引,如果pickle从camCls中获取这个类的信息,就能够把cam保存下来了。但是实际运行发现,pickle找不到camCls。这是为什么呢?

因为pickle保存的时候,只拿到了类的对象,对象中存储了类名cam,pickle就拿着cam去寻找定义,类的内容如果存储在camCls中pickle是不知道的。一般认为,Python中所有的名字都不重要,类的名字、对象的名字都可以随意更换,只要指向的实体的对的,程序就能正常工作,但是保存操作中,名字是重要的,需要依靠名字去找到正确的实体。参考2中也举了类似的例子,说明更换名字的问题。

  • 确保cam类名字正确

正确的起名方法是这样的:

camCls = namedtuple('camCls',['Pmat','K','alpha_c','kc'])

回想一下,平时定义类的时候为什么没有出现这个问题?因为类的定义大多是发生在__main__中的,而类的名字一般也不会随意更换为缩写,就算更换了一般也不会刚好需要保存。但是namedtuple新建的单例对象恰好是可以同时满足这些要求。

总结:

改进后的代码变成了:

camClsAttr = ['Pmat','K','alpha_c','kc']

camCls = namedtuple('camCls', camClsAttr)

class DataMain:

    def __init__(self,cfgGCF):

        ……

    def loadCamAlign(self,datasetPath):

        ……

        cam = {

            'Pmat': np.concatenate(([data[0][0]], [data[1][0]], [data[2][0]]), axis=0),

            'K': np.concatenate(([data[0][1]], [data[1][1]], [data[2][1]]), axis=0),

            'alpha_c': np.concatenate(([data[0][2]], [data[1][2]], [data[2][2]]), axis=0),

            'kc': np.concatenate(([data[0][3]], [data[1][3]], [data[2][3]]), axis=0),

        }



        self.cam = camCls(**cam)

        dataPath = f'{datasetPath}/rigid010203.mat'

        data = scio.loadmat(dataPath)['rigid'][0]

        self.align_mat = data[0][1]

将定义类放在了函数外面,避免对象成为孤儿对象,让pickle保存时也能找到类的定义。这个时候cam不再是一个匿名类,退化成一个普通的类。同样也要注意unpickle的过程中,需要让pickle能以相同的方式找到相同的类型。

参考:

  1. python - Pickle can't pickle a namedtuple - Stack Overflow

  2. python - PicklingError: Can't pickle <class 'decimal.Decimal'>: it's not the same object as decimal.Decimal - Stack Overflow

 类似资料: