pickle是常用的儲存對象和資料的工具,總結使用以來碰到的問題對應的解決方法。尤其是,在儲存defaultdict的時候遇到了問題,在stackoverflow上得到解答,感覺補充了以前的很多不足,是以在此小結鞏固一下。
文章目錄
- 1. cPickle和pickle
- 2. pickle儲存原理粗解
-
- 2.1 儲存defaultdict對象
- 3. pickle2 和 pickle3的差別
1. cPickle和pickle
在python2中,有pickle和cPickle兩個版本,主要差別在于cPickle的底層是C語言實作的,速度更加快,其他基本一緻。
在python3中,隻有保留了最優的版本,包名pickle,減少了開發人員的困惑。
2. pickle儲存原理粗解
以下内容來自于stackoverflow問題Can’t pickle defaultdict中兩個大神“sloth”和“Martijn”的答案,推薦觀看原版。
對于pickle來說任何要儲存的模型或者對象都可以分為資料和代碼兩部分。
- 資料是指資料類對象,比如
等python自帶的資料類型。int, float, dict, set, tuple, list, string
- 代碼就是各種頂級的類或者函數。這樣的類和函數是可以通過import導入的。無名函數、嵌套函數和嵌套類均不是頂級函數,pickle會報錯無法儲存。比較典型的有:lambda表達式、
對于資料部分,可以通過pickle儲存起來然後導入即可。但是代碼部分無法儲存起來,但是會儲存它與資料的關系,當執行
pickle.load
的時候,會根據資料與代碼部分的關系,恢複儲存的資料。是以,通過import将代碼部分定義引入或者将直接代碼部分與
pickle.load
放在同一個檔案中。可以猜想,當我們要儲存的資料或者對象中含有非頂級的類或函數,我們無法在
pickle.load
的時候,引入或者找到對應的代碼定義,導緻加載失敗,是以當儲存時遇到非頂級的類對象和函數均會報錯。
儲存和加載一個頂級類對象和一個以頂級函數為值的dict,代碼入下:
import cPickle as cp
class A(object):
def __init__(self, a):
self._a = a
def print_out(self):
print("this is an instance of modul-level class!", self._a)
a = A(3)
a.print_out()
with open("./data.pkl", "wb") as f:
cp.dump(a, f)
print("----------load----------")
with open("./data.pkl", 'rb') as f:
b = cp.load(f)
b.print_out()
#output
# ('this is an instance of modul-level class!', 3)
# ----------load----------
# ('this is an instance of modul-level class!', 3)
def modul_level_func():
print("this is a modul-level functiion!")
dic = {"1": modul_level_func}
dic['1']()
with open("./dic.pkl", "wb") as f:
cp.dump(dic, f)
print("----------load----------")
with open("./dic.pkl", 'rb') as f:
b_dic = cp.load(f)
b_dic['1']()
#output
# this is a modul-level functiion!
# ----------load----------
# this is a modul-level functiion!
一些非頂級函數和類對象的例子:
# lambda表達式
dic = {"1": lambda : 1}
cp.dump(dic, open("./lambda.pkl", "wb"))
# output
"""
TypeError Traceback (most recent call last)
<ipython-input-8-593d2efd278e> in <module>()
1 dic = {"1": lambda : 1}
----> 2 cp.dump(dic, open("./lambda.pkl", "wb"))
c:\python27\lib\copy_reg.pyc in _reduce_ex(self, proto)
68 else:
69 if base is self.__class__:
---> 70 raise TypeError, "can't pickle %s objects" % base.__name__
71 state = base(self)
72 args = (self.__class__, base, state)
TypeError: can't pickle function objects
"""
# 嵌套函數
def outer():
def inner():
print("inside!")
dic = {'1': inner}
return dic
dic = outer()
dic['1']()
cp.dump(dic, open("inner.pkl", 'wb'))
# output
"""
inside!
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-b11c7f89fcdb> in <module>()
7 dic = outer()
8 dic['1']()
----> 9 cp.dump(dic, open("inner.pkl", 'wb'))
c:\python27\lib\copy_reg.pyc in _reduce_ex(self, proto)
68 else:
69 if base is self.__class__:
---> 70 raise TypeError, "can't pickle %s objects" % base.__name__
71 state = base(self)
72 args = (self.__class__, base, state)
TypeError: can't pickle function objects
"""
2.1 儲存defaultdict對象
defaultdict一般與無參lambda表達式一起使用,儲存時會報“TypeError: can’t pickle function objects”,儲存失敗。解決方案多種:
- 強制類型轉換為dict,但是會失去預設值功能。
- 重定義帶有預設值的dict,不再使用工廠模式産生預設值。
-
自定義對象儲存預設值。
幾種方法都有各自的優缺點,推薦第三種。如果你有更好的方案,請在評論區留言。具體代碼如下:
from collections import defaultdict as ddt
def mean(lis):
return sum(lis) * 1.0 / len(lis)
# 強制類型轉換
a = ddt(lambda :1)
cp.dump(dict(a), open("ddt.pkl", "wb"))
# 重定義帶有預設值的dict,不再使用工廠模式産生預設值。
class dictwithdefault(dict):
def __init__(self, default_value):
super(dictwithdefault, self).__init__()
self._default_value = default_value
def __missing__(self, key):
if callable(self._default_value):
return self._default_value(key)
else:
return self._default_value
class A(object):
def __init__(self, value):
self._value = value
def print_out(self):
print(self._value)
mean_score = mean(student_score_lis)
a_score_dic = dictwithdefault(mean_score)
cp.dump(a_score_dic, open("./daaa.pkl", "wb"))
b_score_dic = dictwithdefault(A)
cp.dump(b_score_dic, open("./daaa.pkl", "wb"))
# 自定義對象儲存預設值。
class default_value(object):
def __init__(self, value):
self._value
def __call__(self):
return self._value
c_ddt = ddt(default_value(mean_score))
cp.dump(c_ddt, open("./daaa.pkl", "wb"))
3. pickle2 和 pickle3的差別
pickle2和pickle3,分别指的是在python2和python3中的pickle,而不是真的存在包名為pickle2和pickle3包。在pickle儲存資料時候,是按照一定的算法協定進行儲存的,這些協定有不同的版本号,比如在pickle2中使用的版本為2,在pickle3中儲存時預設版本号為3,同時由于高版本向低版本相容,是以也支援版本2。是以,當要在pickle2打開pickle3儲存的資料檔案,此檔案一定是要使用協定版本2進行儲存的。
# 在python3中儲存資料,使用協定2
import pickle as pkl
with open("num.pkl", "wb") as f:
pkl.dump(1, f, protocol=2)
# 在python2中可以直接打開
import cPickle as cp
with open("num.pkl", "rb") as f:
cp.dump(1, f)