天天看點

pickle簡介及儲存defaultdict1. cPickle和pickle2. pickle儲存原理粗解3. pickle2 和 pickle3的差別

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來說任何要儲存的模型或者對象都可以分為資料和代碼兩部分。

  • 資料是指資料類對象,比如

    int, float, dict, set, tuple, list, string

    等python自帶的資料類型。
  • 代碼就是各種頂級的類或者函數。這樣的類和函數是可以通過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)