8 保存数据

将数据存入文件

《文件(1)》中,已经学习了如何读写文件。

如果在程序中,有数据要保存到磁盘中,放到某个文件中是一种不错的方法。但是,如果像以前那样存,未免有点凌乱,并且没有什么良好的存储格式,导致数据以后被读出来的时候遇到麻烦,特别是不能让另外的使用者很好地理解。不要忘记了,编程是一个合作的活。还有,存储的数据不一定都是类似字符串、整数那种基础类型的。

总而言之,需要将要存储的对象格式化(或者叫做序列化),才好存好取。这就有点类似集装箱的作用。

所以,要用到本讲中提供的方式。

pickle

pickle 是标准库中的一个模块,还有跟它完全一样的叫做 cpickle,两者的区别就是后者更快。所以,下面操作中,不管是用 import pickle,还是用 import cpickle as pickle,在功能上都是一样的。

  1. >>> import pickle
  2. >>> integers = [1, 2, 3, 4, 5]
  3. >>> f = open("22901.dat", "wb")
  4. >>> pickle.dump(integers, f)
  5. >>> f.close()

pickle.dump(integers, f) 将数据 integers 保存到了文件 22901.dat 中。如果你要打开这个文件,看里面的内容,可能有点失望,但是,它对计算机是友好的。这个步骤,可以称之为将对象序列化。用到的方法是:

pickle.dump(obj,file[,protocol])

  • obj:序列化对象,上面的例子中是一个列表,它是基本类型,也可以序列化自己定义的类型。
  • file:一般情况下是要写入的文件。更广泛地可以理解为为拥有 write() 方法的对象,并且能接受字符串为为参数,所以,它还可以是一个 StringIO 对象,或者其它自定义满足条件的对象。
  • protocol:可选项。默认为 False(或者说 0),是以 ASCII 格式保存对象;如果设置为 1 或者 True,则以压缩的二进制格式保存对象。

下面换一种数据格式,并且做对比:

  1. >>> import pickle
  2. >>> d = {}
  3. >>> integers = range(9999)
  4. >>> d["i"] = integers #下面将这个 dict 格式的对象存入文件
  5. >>> f = open("22902.dat", "wb")
  6. >>> pickle.dump(d, f) #文件中以 ascii 格式保存数据
  7. >>> f.close()
  8. >>> f = open("22903.dat", "wb")
  9. >>> pickle.dump(d, f, True) #文件中以二进制格式保存数据
  10. >>> f.close()
  11. >>> import os
  12. >>> s1 = os.stat("22902.dat").st_size #得到两个文件的大小
  13. >>> s2 = os.stat("22903.dat").st_size
  14. >>> print "%d, %d, %.2f%%" % (s1, s2, (s2+0.0)/s1*100)
  15. 68903, 29774, 43.21%

比较结果发现,以二进制方式保存的文件比以 ascii 格式保存的文件小很多,前者约是后者的 43%。

所以,在序列化的时候,特别是面对较大对象时,建议将 dump() 的参数 True 设置上,虽然现在存储设备的价格便宜,但是能省还是省点比较好。

存入文件,仅是一个目标,还有另外一个目标,就是要读出来,也称之为反序列化。

  1. >>> integers = pickle.load(open("22901.dat", "rb"))
  2. >>> print integers
  3. [1, 2, 3, 4, 5]

就是前面存入的那个列表。再看看被以二进制存入的那个文件:

  1. >>> f = open("22903.dat", "rb")
  2. >>> d = pickle.load(f)
  3. >>> print d
  4. {'i': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, .... #省略后面的数字}
  5. >>> f.close()

还是有自己定义数据类型的需要,这种类型是否可以用上述方式存入文件并读出来呢?看下面的例子:

  1. >>> import cPickle as pickle #cPickle 更快
  2. >>> import StringIO #标准库中的一个模块,跟 file 功能类似,只不过是在内存中操作“文件”
  3. >>> class Book(object): #自定义一种类型
  4. ... def __init__(self,name):
  5. ... self.name = name
  6. ... def my_book(self):
  7. ... print "my book is: ", self.name
  8. ...
  9. >>> pybook = Book("<from beginner to master>")
  10. >>> pybook.my_book()
  11. my book is: <from beginner to master>
  12. >>> file = StringIO.StringIO()
  13. >>> pickle.dump(pybook, file, 1)
  14. >>> print file.getvalue() #查看“文件”内容,注意下面不是乱码
  15. ccopy_reg
  16. reconstructor
  17. q(c_main__
  18. Book
  19. qc__builtin__
  20. object
  21. qNtRq}qUnameqU<from beginner to master>sb.
  22. >>> pickle.dump(pybook, file) #换一种方式,再看内容,可以比较一下
  23. >>> print file.getvalue() #视觉上,两者就有很大差异
  24. ccopy_reg
  25. reconstructor
  26. q(c_main__
  27. Book
  28. qc__builtin__
  29. object
  30. qNtRq}qUnameqU<from beginner to master>sb.ccopy_reg
  31. reconstructor
  32. p1
  33. (c_main__
  34. Book
  35. p2
  36. c__builtin__
  37. object
  38. p3
  39. NtRp4
  40. (dp5
  41. S'name'
  42. p6
  43. S'<from beginner to master>'
  44. p7
  45. sb.

如果要从文件中读出来:

  1. >>> file.seek(0) #找到对应类型
  2. >>> pybook2 = pickle.load(file)
  3. >>> pybook2.my_book()
  4. my book is: <from beginner to master>
  5. >>> file.close()

shelve

pickle 模块已经表现出它足够好的一面了。不过,由于数据的复杂性,pickle 只能完成一部分工作,在另外更复杂的情况下,它就稍显麻烦了。于是,又有了 shelve。

shelve 模块也是标准库中的。先看一下基本操作:写入和读取

  1. >>> import shelve
  2. >>> s = shelve.open("22901.db")
  3. >>> s["name"] = "www.itdiffer.com"
  4. >>> s["lang"] = "python"
  5. >>> s["pages"] = 1000
  6. >>> s["contents"] = {"first":"base knowledge","second":"day day up"}
  7. >>> s.close()

以上完成了数据写入的过程。其实,这更接近数据库的样式了。下面是读取。

  1. >>> s = shelve.open("22901.db")
  2. >>> name = s["name"]
  3. >>> print name
  4. www.itdiffer.com
  5. >>> contents = s["contents"]
  6. >>> print contents
  7. {'second': 'day day up', 'first': 'base knowledge'}

当然,也可以用 for 语句来读:

  1. >>> for k in s:
  2. ... print k, s[k]
  3. ...
  4. contents {'second': 'day day up', 'first': 'base knowledge'}
  5. lang python
  6. pages 1000
  7. name www.itdiffer.com

不管是写,还是读,都似乎要简化了。所建立的对象s,就如同字典一样,可称之为类字典对象。所以,可以如同操作字典那样来操作它。

但是,要小心坑:

  1. >>> f = shelve.open("22901.db")
  2. >>> f["author"]
  3. ['qiwsir']
  4. >>> f["author"].append("Hetz") #试图增加一个
  5. >>> f["author"] #坑就在这里
  6. ['qiwsir']
  7. >>> f.close()

当试图修改一个已有键的值时,没有报错,但是并没有修改成功。要填平这个坑,需要这样做:

  1. >>> f = shelve.open("22901.db", writeback=True) #多一个参数 True
  2. >>> f["author"].append("Hetz")
  3. >>> f["author"] #没有坑了
  4. ['qiwsir', 'Hetz']
  5. >>> f.close()

还用 for 循环一下:

  1. >>> f = shelve.open("22901.db")
  2. >>> for k,v in f.items():
  3. ... print k,": ",v
  4. ...
  5. contents : {'second': 'day day up', 'first': 'base knowledge'}
  6. lang : python
  7. pages : 1000
  8. author : ['qiwsir', 'Hetz']
  9. name : www.itdiffer.com

shelve 更像数据库了。

不过,它还不是真正的数据库。真正的数据库在后面。


总目录

如果你认为有必要打赏我,请通过支付宝:qiwsir@126.com,不胜感激。