之前在网上收集了点资料。放上来。
持久性
对象持久性
Pickle
pickle 模块及其同类模块 cPickle 向 Python 提供了 pickle 支持。后者是用 C 编码的,它具有更好的性能,对于大多数应用程序,推荐使用该模块。我们将继续讨论 pickle ,但本文的示例实际是利用了 cPickle 。由于其中大多数示例要用 Python shell 来显示,所以先展示一下如何导入 cPickle ,并可以作为 pickle 来引用它:
import cPickle as pickle
简单接口
现在已经导入了该模块,接下来让我们看一下 pickle 接口。 pickle 模块提供了以下函数接口:
- dumps(object): 返回一个字符串,它包含一个 pickle 格式的对象;
- loads(string):返回包含在 pickle 字符串中的对象;
- dump(object, file): 将对象写到文件,这个文件可以是实际的物理文件,但也可以是任何类似于文件的对象,这个对象具有 write() 方法,可以接受单个的字符串参数;
- load(file): 返回包含在 pickle 文件中的对象。
>>> import cPickle as pickle >>> t1 = ('this is a string', 42, [1, 2, 3], None) >>> t1 ('this is a string', 42, [1, 2, 3], None) >>> p1 = pickle.dumps(t1) >>> p1 "(S'this is a string'/nI42/n(lp1/nI1/naI2/naI3/naNtp2/n." >>> print p1 (S'this is a string' I42 (lp1 I1 aI2 aI3 aNtp2 . >>> t2 = pickle.loads(p1) >>> t2 ('this is a string', 42, [1, 2, 3], None) >>> p2 = pickle.dumps(t1, True) >>> p2 '(U/x10this is a stringK*]q/x01(K/x01K/x02K/x03eNtq/x02.' >>> t3 = pickle.loads(p2) >>> t3 ('this is a string', 42, [1, 2, 3], None)
注:该文本 pickle 格式很简单,这里就不解释了。事实上,在 pickle 模块中记录了所有使用的约定。我们还应该指出,在我们的示例中使用的都是简单对象,因此使用二进制 pickle 格式不会在节省空间上显示出太大的效率。然而,在实际使用复杂对象的系统中,您会看到,使用二进制格式可以在大小和速度方面带来显著的改进。
接下来,我们看一些示例,这些示例用到了 dump() 和 load() ,它们使用文件和类似文件的对象。这些函数的操作非常类似于我们刚才所看到的 dumps() 和 loads() ,区别在于它们还有另一种能力 — dump() 函数能一个接着一个地将几个对象转储到同一个文件。随后调用 load() 来以同样的顺序检索这些对象。
dump() 和 load() 示例:
>>> a1 = 'apple' >>> b1 = {
1: 'One', 2: 'Two', 3: 'Three'} >>> c1 = ['fee', 'fie', 'foe', 'fum'] >>> f1 = file('temp.pkl', 'wb') >>> pickle.dump(a1, f1, True) >>> pickle.dump(b1, f1, True) >>> pickle.dump(c1, f1, True) >>> f1.close() >>> f2 = file('temp.pkl', 'rb') >>> a2 = pickle.load(f2) >>> a2 'apple' >>> b2 = pickle.load(f2) >>> b2 {
1: 'One', 2: 'Two', 3: 'Three'} >>> c2 = pickle.load(f2) >>> c2 ['fee', 'fie', 'foe', 'fum'] >>> f2.close()
Pickle 的威力
到目前为止,我们讲述了关于 pickle 方面的基本知识。在这一节,将讨论一些高级问题,当您开始 pickle 复杂对象时,会遇到这些问题,其中包括定制类的实例。幸运的是,Python 可以很容易地处理这种情形。
可移植性
>>> pickle.format_version '1.3' >>> pickle.compatible_formats ['1.0', '1.1', '1.2']
多个引用,同一对象
>>> a = [1, 2, 3] >>> b = a >>> a [1, 2, 3] >>> b [1, 2, 3] >>> a.append(4) >>> a [1, 2, 3, 4] >>> b [1, 2, 3, 4] >>> c = pickle.dumps((a, b)) >>> d, e = pickle.loads(c) >>> d [1, 2, 3, 4] >>> e [1, 2, 3, 4] >>> d.append(5) >>> d [1, 2, 3, 4, 5] >>> e [1, 2, 3, 4, 5]
循环引用和递归引用
>>> l = [1, 2, 3] >>> l.append(l) >>> l [1, 2, 3, [...]] >>> l[3] [1, 2, 3, [...]] >>> l[3][3] [1, 2, 3, [...]] >>> p = pickle.dumps(l) >>> l2 = pickle.loads(p) >>> l2 [1, 2, 3, [...]] >>> l2[3] [1, 2, 3, [...]] >>> l2[3][3] [1, 2, 3, [...]]
循环引用:
>>> a = [1, 2] >>> b = [3, 4] >>> a.append(b) >>> a [1, 2, [3, 4]] >>> b.append(a) >>> a [1, 2, [3, 4, [...]]] >>> b [3, 4, [1, 2, [...]]] >>> a[2] [3, 4, [1, 2, [...]]] >>> b[2] [1, 2, [3, 4, [...]]] >>> a[2] is b 1 >>> b[2] is a 1 >>> f = file('temp.pkl', 'w') >>> pickle.dump((a, b), f) >>> f.close() >>> f = file('temp.pkl', 'r') >>> c, d = pickle.load(f) >>> f.close() >>> c [1, 2, [3, 4, [...]]] >>> d [3, 4, [1, 2, [...]]] >>> c[2] [3, 4, [1, 2, [...]]] >>> d[2] [1, 2, [3, 4, [...]]] >>> c[2] is d 1 >>> d[2] is c 1
注意,如果分别 pickle 每个对象,而不是在一个元组中一起 pickle 所有对象,会得到略微不同(但很重要)的结果
分别 pickle vs. 在一个元组中一起 pickle:
>>> f = file('temp.pkl', 'w') >>> pickle.dump(a, f) >>> pickle.dump(b, f) >>> f.close() >>> f = file('temp.pkl', 'r') >>> c = pickle.load(f) >>> d = pickle.load(f) >>> f.close() >>> c [1, 2, [3, 4, [...]]] >>> d [3, 4, [1, 2, [...]]] >>> c[2] [3, 4, [1, 2, [...]]] >>> d[2] [1, 2, [3, 4, [...]]] >>> c[2] is d 0 >>> d[2] is c 0
相等,但并不总是相同。
正如在上一个示例所暗示的,只有在这些对象引用内存中同一个对象时,它们才是相同的。在 pickle 情形中,每个对象被恢复到一个与原来对象相等的对象,但不是同一个对象。换句话说,每个 pickle 都是原来对象的一个副本:
作为原来对象副本的被恢复的对象:
>>> j = [1, 2, 3] >>> k = j >>> k is j 1 >>> x = pickle.dumps(k) >>> y = pickle.loads(x) >>> y [1, 2, 3] >>> y == k 1 >>> y is k 0 >>> y is j 0 >>> k is j 1
>>> f = file('temp.pkl', 'w') >>> pickler = pickle.Pickler(f) >>> pickler.dump(a)
0x89b0bb8>
>>> pickler.dump(b)
0x89b0bb8>
>>> f.close()
>>> f = file(
'temp.pkl',
'r')
>>> unpickler = pickle.Unpickler(f)
>>> c = unpickler.load()
>>> d = unpickler.load()
>>> c[
2] [
3,
4, [
1,
2, [...]]]
>>> d[
2] [
1,
2, [
3,
4, [...]]]
>>> c[
2]
is d
1
>>> d[
2]
is c
1
不可 pickle 的对象
>>> f = file('temp.pkl', 'w') >>> p = pickle.dumps(f) Traceback (most recent call last): File "", line 1, in ? File "/usr/lib/python2.2/copy_reg.py", line 57, in _reduce raise TypeError, "can't pickle %s objects" % base.__name__ TypeError: can't pickle file objects
类实例
与 pickle 简单对象类型相比,pickle 类实例要多加留意。这主要由于 Python 会 pickle 实例数据(通常是 dict 属性)和类的名称,而不会 pickle 类的代码。当 Python unpickle 类的实例时,它会试图使用在 pickle 该实例时的确切的类名称和模块名称(包括任何包的路径前缀)导入包含该类定义的模块。另外要注意,类定义必须出现在模块的最顶层,这意味着它们不能是嵌套的类(在其它类或函数中定义的类)。
当 unpickle 类的实例时,通常不会再调用它们的 init() 方法。相反,Python 创建一个通用类实例,并应用已进行过 pickle 的实例属性,同时设置该实例的 class 属性,使其指向原来的类。
对 Python 2.2 中引入的新型类进行 unpickle 的机制与原来的略有不同。虽然处理的结果实际上与对旧型类处理的结果相同,但 Python 使用 copy_reg 模块的 _reconstructor() 函数来恢复新型类的实例。
如果希望对新型或旧型类的实例修改缺省的 pickle 行为,则可以定义特殊的类的方法 getstate() 和 setstate() ,在保存和恢复类实例的状态信息期间,Python 会调用这些方法。在以下几节中,我们会看到一些示例利用了这些特殊的方法。
现在,我们看一个简单的类实例。首先,创建一个 persist.py 的 Python 模块,它包含以下新型类的定义:
新型类的定义
class Foo(object): def __init__(self, value): self.value = value
>>> import cPickle as pickle >>> from Orbtech.examples.persist import Foo >>> foo = Foo('What is a Foo?') >>> p = pickle.dumps(foo) >>> print p ccopy_reg _reconstructor p1 (cOrbtech.examples.persist Foo p2 c__builtin__ object p3 NtRp4 (dp5 S'value' p6 S'What is a Foo?' sb.
>>> import cPickle as pickle >>> f = file('temp.pkl', 'r') >>> foo = pickle.load(f) Traceback (most recent call last): File "", line 1, in ? AttributeError: 'module' object has no attribute 'Foo'
>>> import cPickle as pickle >>> f = file('temp.pkl', 'r') >>> foo = pickle.load(f) Traceback (most recent call last): File "", line 1, in ? ImportError: No module named persist
我们会在下面 模式改进这一节提供一些技术来管理这类更改,而不会破坏现有的 pickle。
pickle类实例的特殊的状态方法
前面提到对一些对象类型(譬如,文件对象)不能进行 pickle。处理这种不能 pickle 的对象的实例属性时可以使用特殊的方法( getstate() 和 setstate() )来修改类实例的状态。这里有一个 Foo 类的示例,我们已经对它进行了修改以处理文件对象属性:
处理不能 pickle 的实例属性:
class Foo(object): def __init__(self, value, filename): self.value = value self.logfile = file(filename, 'w') def __getstate__(self): """Return state values to be pickled.""" f = self.logfile return (self.value, f.name, f.tell()) def __setstate__(self, state): """Restore state from the unpickled state values.""" self.value, name, position = state f = file(name, 'w') f.seek(position) self.logfile = f
pickle Foo 的实例时,Python 将只 pickle 当它调用该实例的 getstate() 方法时返回给它的值。类似的,在 unpickle 时,Python 将提供经过 unpickle 的值作为参数传递给实例的 setstate() 方法。在 setstate() 方法内,可以根据经过 pickle 的名称和位置信息来重建文件对象,并将该文件对象分配给这个实例的 logfile 属性。
模式改进
类名的更改
def __setstate__(self, state): self.__dict__.update(state) self.__class__ = NewClassName
当 unpickle 现有实例时,Python 将查找原来类的定义,并调用实例的 setstate() 方法,同时将给新的类定义重新分配该实例的 class 属性。一旦确定所有现有的实例都已经 unpickle、更新和重新 pickle 后,可以从源代码模块中除去旧的类定义。
属性的添加和删除
这些特殊的状态方法 getstate() 和 setstate() 再一次使我们能控制每个实例的状态,并使我们有机会处理实例属性中的更改。让我们看一个简单的类的定义,我们将向其添加和除去一些属性。
最初的类定义:
class Person(object): def __init__(self, firstname, lastname): self.firstname = firstname self.lastname = lastname
class Person(object): def __init__(self, fullname): self.fullname = fullname def __setstate__(self, state): if 'fullname' not in state: first = '' last = '' if 'firstname' in state: first = state['firstname'] del state['firstname'] if 'lastname' in state: last = state['lastname'] del state['lastname'] self.fullname = " ".join([first, last]).strip() self.__dict__.update(state)
在这个示例,我们添加了一个新的属性 fullname ,并除去了两个现有的属性 firstname 和 lastname 。当对先前进行过 pickle 的实例执行 unpickle 时,其先前进行过 pickle 的状态会作为字典传递给 setstate() ,它将包括 firstname 和 lastname 属性的值。接下来,将这两个值组合起来,并将它们分配给新属性 fullname 。在这个过程中,我们删除了状态字典中旧的属性。更新和重新 pickle 先前进行过 pickle 的所有实例之后,现在可以从类定义中除去 setstate() 方法。
模块的修改
在概念上,模块的名称或位置的改变类似于类名称的改变,但处理方式却完全不同。那是因为模块的信息存储在 pickle 中,而不是通过标准的 pickle 接口就可以修改的属性。事实上,改变模块信息的唯一办法是对实际的 pickle 文件本身执行查找和替换操作。至于如何确切地去做,这取决于具体的操作系统和可使用的工具。很显然,在这种情况下,您会想备份您的文件,以免发生错误。但这种改动应该非常简单,并且对二进制 pickle 格式进行更改与对文本 pickle 格式进行更改应该一样有效。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220226.html原文链接:https://javaforall.net
