python __slots__ 詳解(上篇)


轉自:http://blog.csdn.net/sxingming/article/details/52892640

 

python中的new-style class要求繼承Python中的一個內建類型,一般繼承object,也可以繼承list或者dict等其他的內建類型。
在python新式類中,可以定義一個變量__slots__,它的作用是阻止在實例化類時為實例分配dict,

默認情況下每個類都會有一個dict,通過__dict__訪問,這個dict維護了這個實例的所有屬性,舉例如下:

 

[python]  view plain copy
 
  1. class base(object):  
  2.     var=#類變量  
  3.     def __init__(self):  
  4.         pass  
  5.   
  6. b=base()  
  7. print b.__dict__  
  8. b.x=#添加實例變量  
  9. print b.__dict__  

運行結果:
{ }
{'x': 2}
可見:實例的dict只保持實例的變量,對於類的屬性是不保存的,類的屬性包括變量和函數。
由於每次實例化一個類都要分配一個新的dict,因此存在空間的浪費,因此有了__slots__。
__slots__是一個元組,包括了當前能訪問到的屬性。
當定義了slots后,slots中定義的變量變成了類的描述符,相當於java,c++中的成員變量聲明,
類的實例只能擁有slots中定義的變量,不能再增加新的變量。注意:定義了slots后,就不再有dict。如下:

 

 

[python]  view plain copy
 
  1. class base(object):  
  2.     __slots__=('x')  
  3.     var=8  
  4.     def __init__(self):  
  5.         pass  
  6.   
  7. b=base()  
  8. b.x=88 #添加實例變量  
  9. print b.x  
  10. #b.y=99 #無法添加slots之外的變量 (AttributeError: 'base' object has no attribute 'y')  
  11. #print b.__dict__ #定義了__slots__后,就不再有__dict__ (AttributeError: 'base' object has no attribute '__dict__')  

運行結果:
88
如果類變量與slots中的變量同名,則該變量被設置為readonly!!!如下:

 

 

[python]  view plain copy
 
  1. class base(object):  
  2.     __slots__=('y')  
  3.     y=22 # y是類變量,y與__slots__中的變量同名  
  4.     var=11  
  5.     def __init__(self):  
  6.         pass  
  7.       
  8. b=base()  
  9. print b.y  
  10. print base.y  
  11. #b.y=66 #AttributeError: 'base' object attribute 'y' is read-only  

運行結果:
22
22
Python是一門動態語言,可以在運行過程中,修改實例的屬性和增刪方法。一般,任何類的實例包含一個字典__dict__,
Python通過這個字典可以將任意屬性綁定到實例上。有時候我們只想使用固定的屬性,而不想任意綁定屬性,
這時候我們可以定義一個屬性名稱集合,只有在這個集合里的名稱才可以綁定。__slots__就是完成這個功能的。

 

 

[python]  view plain copy
 
  1. class test_slots(object):  
  2.     __slots__='x','y'  
  3.     def printHello(self):  
  4.         print 'hello!'  
  5.   
  6. class test(object):  
  7.     def printHello(self):  
  8.         print 'hello'  
  9.   
  10. print dir(test_slots) #可以看到test_slots類結構里面包含__slots__,x,y  
  11. print dir(test)#test類結構里包含__dict__  
  12. print '**************************************'  
  13. ts=test_slots()  
  14. t=test()  
  15. print dir(ts) #可以看到ts實例結構里面包含__slots__,x,y,不能任意綁定屬性  
  16. print dir(t) #t實例結構里包含__dict__,可以任意綁定屬性  
  17. print '***************************************'  
  18. ts.x=11 #只能綁定__slots__名稱集合里的屬性  
  19. t.x=12 #可以任意綁定屬性  
  20. print ts.x,t.x  
  21. ts.y=22 #只能綁定__slots__名稱集合里的屬性  
  22. t.y=23  #可以任意綁定屬性  
  23. print ts.y,t.y  
  24. #ts.z=33 #無法綁定__slots__集合之外的屬性(AttributeError: 'test_slots' object has no attribute 'z')  
  25. t.z=34 #可以任意綁定屬性  
  26. print t.z   

運行結果:
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'printHello', 'x', 'y']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'printHello']
**************************************
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'printHello', 'x', 'y']
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'printHello']
***************************************
11 12
22 23
34

正如上面所說的,默認情況下,Python的新式類和經典類的實例都有一個dict來存儲實例的屬性。這在一般情況下還不錯,而且非常靈活,
乃至在程序中可以隨意設置新的屬性。但是,對一些在”編譯”前就知道有幾個固定屬性的小class來說,這個dict就有點浪費內存了。
當需要創建大量實例的時候,這個問題變得尤為突出。一種解決方法是在新式類中定義一個__slots__屬性。
__slots__聲明中包含若干實例變量,並為每個實例預留恰好足夠的空間來保存每個變量;這樣Python就不會再使用dict,從而節省空間。

【使用memory_profiler模塊,memory_profiler模塊是在逐行的基礎上,測量代碼的內存使用率。盡管如此,它可能使得你的代碼運行的更慢。使用裝飾器@profile來標記哪個函數被跟蹤。】

下面,我們看一個例子:

[python]  view plain copy
 
  1. from  memory_profiler import profile  
  2. class A(object): #沒有定義__slots__屬性  
  3.     def __init__(self,x):  
  4.         self.x=x  
  5.  
  6. @profile  
  7. def main():  
  8.     f=[A(523825) for i in range(100000)]  
  9.   
  10. if __name__=='__main__':  
  11.     main()  

運行結果,如下圖:

第2列表示該行執行后Python解釋器的內存使用情況,第3列表示該行代碼執行前后的內存變化。
在沒有定義__slots__屬性的情況下,該代碼共使用了20.8MiB內存。
從結果可以看出,內存使用是以MiB為單位衡量的,表示的mebibyte(1MiB = 1.05MB)

[python]  view plain copy
 
  1. from  memory_profiler import profile  
  2. class A(object):#定義了__slots__屬性  
  3.     __slots__=('x')  
  4.     def __init__(self,x):  
  5.         self.x=x  
  6.  
  7. @profile  
  8. def main():  
  9.     f=[A(523825) for i in range(100000)]  
  10.   
  11. if __name__=='__main__':  
  12.     main()  

運行結果,如下圖:

可以看到,在定義了__slots__屬性的情況下,該代碼共使用了6.1MiB內存,比上面的20.8MiB節省了很多內存!
綜上所述,在確定了類的屬性固定的情況下,可以使用__slots__來優化內存。
提醒:不要貿然進行這個優化,把它用在所有地方。這種做法不利於代碼維護,而且只有生成數以千計的實例的時候才會有明顯效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM