創(chuàng)建大量對象時節(jié)省內(nèi)存方法
問題:你的程序要創(chuàng)建大量 可能上百萬 的對象,導致占用很大的內(nèi)存
對于主要是用來當成簡單的數(shù)據(jù)結(jié)構(gòu)的類而言,你可以通過給類添加__slots__
屬性來極大的減少實例所占的內(nèi)存,比如:
class Date:
__slots__ = ['year','month','day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
- 當你定義
__slots__
后,Python就會為實例使用一種更加緊湊的內(nèi)容表示。實例通過一個很小的固定大小的數(shù)組來構(gòu)建,而不是為每個實例定義一個字典,這跟元組或列表很類似。在__slots__
中列出的屬性名在內(nèi)部被映射到這個數(shù)組的指定小標上。使用__slots__
一個不好的地方就是我們不能再給實例添加新的屬性了,只能使用在__slots__
中定義的那些屬性名。好處就是內(nèi)存占用跟一個元組差不多了,省內(nèi)存。
討論
盡管__slots__
看上去是一個很有用的特性,很多時候你還是得減少對它的使用沖動。Python的很多特性都依賴于普通的基于字典的實現(xiàn),用了__slots__
的實例就不用調(diào)用__dict__
屬性了。另外,定義__slots__
后的類不再支持一些普通類特性了,比如多繼承。大多數(shù)情況下,你應(yīng)該只在那些經(jīng)常被使用到的用作數(shù)據(jù)結(jié)構(gòu)的類上定義__slots__
,比如在程序中需要創(chuàng)建某個類的幾百萬個實例對象 。
關(guān)于__slots__
的一個常見誤區(qū)是它可以作為一個封裝工具來防止用戶給實例增加新的屬性。盡管可以達到這樣的目的,但是這個并不是它的初衷。__slots__
更多的是用來作為一個內(nèi)存優(yōu)化工具。
創(chuàng)建可管理的屬性
問題:你想給某個實例attribute增加除訪問與修改之外的其他處理邏輯,比如類型檢查或合法性驗證。
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function(optional)
@first_name.deleter
def first_name(self):
raise AttributeError('Can not delete attribute')
- 上述代碼中有三個相關(guān)聯(lián)的方法,這三個方法的名字都必須一樣。第一個方法是一 個getter函數(shù),它使得first_name成為一個屬性。其他兩個方法給first_name屬性添加了setter和deleter函數(shù)。需要強調(diào)的是只有在first_name屬性被創(chuàng)建后,后面的兩個裝飾器@first_name.setter和@first_name.deleter才能被定義。
- property的關(guān)鍵特征就是它看上去跟普通attribute沒什么兩樣,但是訪問這個property時會自動觸發(fā)相應(yīng)的setter,getter, deleter方法
討論
一個property屬性其實就是一系列相關(guān)綁定方法的集合。如果你去查看擁有property的類,就會發(fā)現(xiàn)property本身的fget、fset和fdel 屬性就是類里面的普通方法。
eg:
In [148]: Person.first_name.fset
Out[148]: <function main.Person.first_name(self, value)>
In [149]: Person.first_name.fget
Out[149]: <function main.Person.first_name(self)>
class Person:
def __init__(self, first_name):
self.set_first_name(first_name)
# Getter function
def get_first_name(self):
return self._first_name
# Setter function
def set_first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function(optional)
def del_first_name(self):
raise AttributeError('Can not delete attribute')
# Make a property from existing get/set/del methods
name = property(get_first_name, set_first_name, del_first_name())
- properties還是一種定義動態(tài)計算attribute的方法。這種類型的attribute并不會 被實際的存儲,而是在需要的時候計算出來。
調(diào)用父類的方法
問題:你想在子類中調(diào)用父類的某個已經(jīng)被覆蓋的方法。
- 為了調(diào)用父類 超類 的一個方法,可以使用super()函數(shù),比如:
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam() # call parent spam()
- super()函數(shù)的一個常見用法是在
__init__()
方法中確保父類被正確的初始化了:
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
- super()的另外一個常見用法出現(xiàn)在覆蓋python特殊方法的代碼中,比如:
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute look up to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value)
else:
setattr(self._obj, name, value)
在上面代碼中,
__setattr__()
的實現(xiàn)包含一個名字檢查。如果某個屬性名以下劃線_開頭,就通過super()
調(diào)用原始的__setattr__()
,否則的話就委派給內(nèi)部的 代理對象self.obj
去處理。這看上去有點意思,因為就算沒有顯式的指明某個類的父類(個人認為這肯定是調(diào)用了默認基類object的方法了,其實還是有父類的),super()
仍然可以有效的工作。
上述例子中,B中也可以直接使用父類名來調(diào)用如
A.__init__(self)
但這樣涉及復雜多繼承情況就會有可能重復執(zhí)行父類方法了(eg: A(Base), B(Base), C(A, B)這樣的情況下C的init里同時調(diào)用了A和B的同個方法,A、B這個方法又用了 Base來直接調(diào)用Base方法)當你使用super()函數(shù)時, Python會在
MRO
列表(C3線性化算法來實現(xiàn)的)上繼續(xù)搜索下一個類。只要每個重定義的方法統(tǒng)一使用super()
并只調(diào)用它一次,那么控制流最終會遍歷完整個MRO
列表,每個方法也只會被調(diào)用一次。這也是為什么在第二個例子中你不會調(diào)用兩次Base.__init__()
的原因。
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A, B):
def __init__(self):
super().__init__() # Only one call to super() here
print('C.__init__')
C.mro()
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
# <class '__main__.Base'>, <class 'object'>]
c = C()
# Base.__init__
# B.__init__
# A.__init__
# C.__init__