Python 的 Magic Methods 指南(10)

Pickling: 赶快到盐水中泡泡

(译者注:pickle是用来直接保存Python对象的模块,在英文中有“腌制”的意思)

让我们深入挖掘pickling方法。假设你想保存一个字典并在之后检索它:你可以把它写入一个文件中,小心确保其有正确的语法,之后用exec()或者读取文件来检索它。但这很有可能是相当危险的:如果你将重要数据保存在纯文本中,它可能会损坏或者发生各种各样的改变,有些会让你的程序崩溃,有些甚至会在你的电脑上运行恶意代码。因此,我们应该使用 pickle方法:

import pickledata = {'foo': [1, 2, 3], 'bar': ('Hello', 'world!'), 'baz': True}jar = open('data.pkl', 'wb')pickle.dump(data, jar) # write the pickled data to the file jarjar.close()

几个小时之后,我们希望找回这些数据,现在我们只需unpickle它:

import picklepkl_file = open('data.pkl', 'rb') # connect to the pickled datadata = pickle.load(pkl_file) # load it into a variableprint datapkl_file.close()

发生了什么?正如你所想的那样,我们现在找回了data。

现在,我们要注意一点:pickle并不完美。被pickle序列化的文件很容易被意外或是有意损坏。pickle模块可能比一般的纯文本文件要来的安全,但它仍然可能会被利用去运行恶意代码。而且它在各个Python版本之间是不兼容的,所以不要传送pkl文件并妄想其他人可以打开它。但是,pickle确实是处理缓存和其他序列化任务的强有力工具。

用Pickle序列化你的对象

pickle模块不仅可以用于内建类型,它还可以以用于序列化任何遵循pickle协议的类。pickle协议为Python对象定义了四个可选的方法,你可以重载这些方法来定义它们的行为(这和C扩展有些不同,但这不在我们的讨论范围之内):

__getinitargs__(self)

如果你想在你的类被unpickle的时候执行__init__方法,你可以重载__getinitargs__方法,它会返回一个元组,包含你想传给__init__方法的参数。注意,这种方法只适用于旧式的Python类型(译者注:区别于2.2中引入的新式类)。

__getnewargs__(self)

对于新式类,在unpickle的时候你可以决定传给__new__方法的参数。以上方法可以返回一个包含你想传给__new__方法的参数元组。

__getstate__(self)

除了储存__dict__中的原来的那些变量,你可以自定义使用pickle序列化对象时想要储存的额外属性。这些属性将在你unpickle文件时被__setstate__方法使用。

__setstate__(self, state)

当文件被unpickle时,其中保存的对象属性不会直接被写入对象的__dict中,而是会被传入这个方法。这个方法和__getstate__是配套的:当他们都被定义了的时候,你可以任意定义对象被序列化存储时的状态。

__reduce__(self)

当你定义扩展类(使用C语言实现的Python扩展类)时,可以通过实现__reduce__函数来控制pickle的数据。如果__reduce__()方法被定义了,在一个对象被pickle时它将被调用。如果它返回一个字符串,那么pickle在将在全局空间中搜索对应名字的对象进行pickle;它还可以返回一个元组,包含2-5个元素: 一个可以用来重建该对象的可调用对象,一个包含有传给该可调用对象参数的元组,传给__setstate__方法的参数(可选),一个用于待pickle对象列表的迭代器(译者注:这些对象会被append到原来对象的后面)(可选)调用对象,一个包含有传给该可调用对象参数的元组,传给__setstate__方法的参数(可选),一个用于待pickle对象列表的迭代器(译者注:这些对象会被append到原来对象的后面)(可选),一个用于待pickle的字典的迭代器(可选)。

__reduce_ex__(self)

__reduce_ex__是为兼容性而设计的。如果它被实现了,__reduce_ex__将会取代__reduce__在pickle时被执行。__reduce__可以同时被实现以支持那些不支持__reduce_ex__的老版本pickling API。

(译者注:这段说的不是非常清楚,感兴趣可以去看文档,一般来说只要使用上一节中的方法就足够了,注意在反序列化之前要先有对象的定义,否则会出错)

一个例子

我们以Slate为例,这一段记录一个值以及这个值是何时被写入的程序,但是,这个Slate有一点特殊的地方,就是当前值不会被保存。

import time class Slate: '''Class to store a string and a changelog, and forget its value when pickled.''' def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = {} def change(self, new_value): # Change the value. Commit last value to history self.history[self.last_change] = self.value self.value = new_value self.last_change = time.asctime() def print_changes(self): print 'Changelog for Slate object:' for k, v in self.history.items(): print '%s\t %s' % (k, v) def __getstate__(self): # Deliberately do not return self.value or self.last_change. # We want to have a "blank slate" when we unpickle. return self.history def __setstate__(self, state): # Make self.history = state and last_change and value undefined self.history = state self.value, self.last_change = None, None 总结

这份指南的目的是希望为所有人带来一些知识,即使你是Python大牛或者精通面向对象开发。如果你是一个Python初学者,阅读这篇文章之后,你已经获得了编写丰富,优雅,灵活的类的知识基础了。如果你是一个有一些经验的Python程序员,你可能会发现一些能让你写的代码更简洁的方法。如果你是一个曾经使用过Python的程序员,该文可能会帮助你知晓一些新的概念和方法以及帮助你减少编写代码量的方式。如果你是一个Python专家,该文会帮助你想起来一些你已经遗忘的只是,或者一些你还没听说过的新功能。不惯你现在有多少经验,我希望这次对于Python特殊方法的旅程是真正的一次神奇之旅。(双关语的感觉真是棒!)

附录 1: 如何调用Magic Method

一些magic method已经映射到自带的方法(built-in functions);这种情况下如何调用他们是显而易见的。然而,在其他情况下,调用它们就不那么容易了。本附录致力于展示能够调用magic method的一些不被察觉的语法。

Magic Method何时被调用(例子)Explanation
__new__(cls [,...])   instance = MyClass(arg1, arg2)   __new__ is called on instance creation  
__init__(self [,...])   instance = MyClass(arg1, arg2)   __init__ is called on instance creation  
__cmp__(self, other)   self == other, self > other, etc.   Called for any comparison  
__pos__(self)   +self   Unary plus sign  
__neg__(self)   -self   Unary minus sign  
__invert__(self)   ~self   Bitwise inversion  
__index__(self)   x[self]   Conversion when object is used as index  
__nonzero__(self)   bool(self)   Boolean value of the object  
__getattr__(self, name)   self.name # name doesn't exist   Accessing nonexistent attribute  
__setattr__(self, name, val)   self.name = val   Assigning to an attribute  
__delattr__(self, name)   del self.name   Deleting an attribute  
__getattribute__(self, name)   self.name   Accessing any attribute  
__getitem__(self, key)   self[key]   Accessing an item using an index  
__setitem__(self, key, val)   self[key] = val   Assigning to an item using an index  
__delitem__(self, key)   del self[key]   Deleting an item using an index  
__iter__(self)   for x in self   Iteration  
__contains__(self, value)   value in self,value not in self   Membership tests using in  
__call__(self [,...])   self(args)   "Calling" an instance  
__enter__(self)   with self as x:   with statement context managers  
__exit__(self, exc, val, trace)   with self as x:   with statement context managers  
__getstate__(self)   pickle.dump(pkl_file, self)   Pickling  
__setstate__(self)   data = pickle.load(pkl_file)   Pickling  

希望这个表能够解决你可能会遇到的哪个语法调用哪个magic method的问题。

附录 2: Python 3中的改动

这里我们列举出一些Python 3与2.x在对象模型上主要的的不同之处。

因为Python 3中string和unicode直接已经没有差别,__unicode__已经不存在了,并且__bytes__(它的行为与__str__和__unicode__类似)成为新的自带方法来构造byte数组

因为Python 3里面的division默认变成了true division,__div__在Python3中不存在了。

__coerce__被去除掉了是因为它与其他magic method冗余并且造成了行为混淆。

__cmp__被去除掉了是因为它与其他magic method冗余。

__nonzero__被重命名为__bool__

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/cb1b2f78dd9df2088d69413cabe7ac94.html