从pickle看python类成员的动态加载和类的定位

来源:互联网 发布:湖南乐知英语 编辑:程序博客网 时间:2024/05/21 04:17

从pickle看python类成员的动态加载和类的定位(转)

(2012-09-12 17:04:17)
转载
标签:

it

 
  pickle是Python轻便的对象序列化工具。使用pickle可以方便地把python对象写入文件对象中,或者像soap那样在socket间传送。
 
 按照python的一贯作风,类的成员在使用前不会分配和占用内存空间。这一点使用pickle可以看得很清楚。
 
  例如有类矩形Rect
 
#文件Rect_Module.py 
class Rect: 
    def __init__(self,a_width , a_height): 
       self.m_width   = a_width 
       self.m_height = a_height 
    def get_area(self): 
       return self.m_width *self.m_height 
    defset_color(self,color): 
       self.m_color = color 

 
 
  该矩形类定义在__init__时候了两个成员高度m_height和m_width宽度,如果被设置颜色后,则又生成了m_color成员。
 
  使用pickle来dump一个Rect对象:
#pickle_dump.py
import pickle 
import Rect_Module 

#生成一个3*4的矩形,然后pickle之 
if __name__ == "__main__": 
     
    myrect =Rect_Module.Rect(3,4) 
    print "area is :",myrect.get_area() 
    
    fout =open("myrect.pkl","w") 
   pickle.dump(myrect,fout) 
    fout.close()   
 
  运行pickle_dump.py后,生成myrect.pkl文件,我们可以打开来看看:
 
(iRect_Module 
Rect 
p0 
(dp1 
S'm_height' 
p2 
I4 
sS'm_width' 
p3 
I3 
sb.
 
  Pickle文件简单剖析
 
 在pickle生成的文件中,很容易看到最前面红色和土黄色的分别是模块名和类名;
后面不远处是属性m_height和m_width,属性的后面是它的值:I4、I3是不是表示Integer的前缀?改变一下参数就知道了。
 
 
#pickle_dump.py 

import pickle    
import Rect_Module    

if __name__ == "__main__":   
          
       myrect = Rect_Module.Rect(3.99999,4)   
       print "area is :" ,myrect.get_area()   
        
       fout = open("myrect.pkl","w")   
       pickle.dump(myrect,fout)   
       fout.close()
 
 上面代码改变了传入参数的类型,希望dump出来的文件中有不同的类型前缀。dump出来的文件如下:
 
(iRect_Module 
Rect 
p0 
(dp1 
S'm_height' 
p2 
I4 
sS'm_width' 
p3 
F3.9999899999999999 
sb.
 
 果不其然,传入3.99999构造Rect时,pickle文件中的值的字段变成了F3.9999****,这里F明显是Float的意思。实际上,如果需要pickle的对象成员为一个自定义类的类型,pickle文件里也会保留类名信息,以及类成员的内部结构。
 
  Python的类属性动态加载
 
 看了上面几个例子,我们会留意到Rect的set_color中涉及到了m_color成员实际上并没有生成,因为我们没有调用set_color方法。这是python的一个特性:成员只有在初次被引用的时候才会初始化。没有调用过的set_color对象是没有m_color属性的,如果你希望它一定有,那么只好在__init__中引用它了。这是python一个重要特点,是优是劣就见仁见智了。
 
  下面设置一下颜色
 
#pickle_dump.py 

import pickle    
import Rect_Module    

if __name__ == "__main__":   
          
       myrect = Rect_Module.Rect(3.99999,4)   
       print "area is :" ,myrect.get_area()   
       myrect.set_color("RED") 
       fout = open("myrect.pkl","w")   
       pickle.dump(myrect,fout)   
       fout.close()       
 
  得到的pickle文件
 
(iRect_Module 
Rect 
p0 
(dp1 
S'm_height' 
p2 
I4 
sS'm_color' 
p3 
S'RED' 
p4 
sS'm_width' 
p5 
F3.9999899999999999 
sb.
 
 上文件的阴影部分清楚地指示了m_color文件被生成了。这里pickle生动地验证了python的“惰性”加载策略。
 
 
 对象序列化有个重要的问题是:从文件中还原对象如何得到它的类信息。从上面的pickle文件看,文件中绝不可能存储对象的类的具体信息,只是存储了模块名和类名。
 从pickle文件中装载对象非常简单,因为文件中已经有模块名和类名了,所以甚至无须importRect_Module。这里尝试把上面例子生成的pickle文件读入:
 
#pickle_load.py
import pickle 

if __name__ == "__main__": 
     
    fin   = open("myrect.pkl") 
    load_from_file =pickle.load(fin) 
     
    print "area is :",load_from_file.get_area() 
       
 
  运行结果area is : 15.99996,证明对象加载正确。
 
 如果把Rect_Module.py文件改名,就会出现“类型找不到”类的错误。
 
  修改类接口
 
 Python这样做可想而知是非常方便的,但这样做会出现一个非常严重的问题。一个pickle文件传到网络的另一端的时候,使用时需要把相应的类文件也传过去。类文件传过去以后,另一方的使用者就可以自由地改动类的部分属性和方法了。
 
  下面通过试验看看是不是这样:
 
  修改类的get_area方法,再load对象
 

class Rect: 
    def __init__(self,a_width , a_height): 
       self.m_width   = a_width 
       self.m_height = a_height 
    def get_area(self): 
       return self.m_width * self.m_height *2 
    defset_color(self,color): 
       self.m_color = color
 
  改变了Rect类的get_area方法,面积的算法为长*宽*2。
 
  此时再运行pickle_load.py,运行结果如下:
 
area is : 31.99992
 
  惊喜地,类的方法被改变了,pickle文件中的对象依然能够正确加载。这个特性非常的灵活,而又非常的“不安全”。用户可以在理解了源代码的基础上,可以任意修改类的行为。这可能就是自由软件、自由语言的含义吧。
 
 个人认为,在pickle文件中加入类的hash签名校验,便可以防止使用不同的类来加载pickle原本的对象。以python设计者的智商,这可能是考虑过的了,应该只是他们不喜欢对语言加以约束,让python更自由,灵活,简约
原创粉丝点击