先看一個例子
class HauntedBus:
"""備受幽靈乘客折磨的校車"""
def __init__(self, passengers=[]):
self.passengers = passengers
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
測試
>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.passengers
['Alice', 'Bill']
>>> bus1.pick('Charlie')
>>> bus1.drop('Alice')
>>> bus1.passengers
['Bill', 'Charlie']
>>> bus2 = HauntedBus()
>>> bus2.pick('Carrie')
>>> bus2.passengers
['Carrie']
>>> bus3 = HauntedBus()
>>> bus3.passengers
['Carrie']
>>> bus3.pick('Dave')
>>> bus2.passengers
['Carrie', 'Dave']
>>> bus2.passengers is bus3.passengers
True
>>> bus1.passengers
['Bill', 'Charlie']
出現(xiàn)這個問題的根源是箕憾,默認值在定義函數(shù)時計算(通常在加載模塊時)牡借,因此默認值變成了函數(shù)對象的屬性。因此袭异,如果默認值是可變對象钠龙,而且修改了它的值,那么后續(xù)的函數(shù)調(diào)用都會受到影響.
可變默認值導致的這個問題說明了為什么通常使用 None 作為接收可變值的參數(shù)的默認值.
對上面的類進行改進,規(guī)避上述的問題:
class TwilightBus:
"""讓乘客銷聲匿跡的校車"""
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = passengers
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
測試
>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
>>> bus = TwilightBus(basketball_team)
>>> bus.drop('Tina')
>>> bus.drop('Pat')
>>> basketball_team
['Sue', 'Maya', 'Diana']
TwilightBus 違反了設計接口的最佳實踐御铃,即“最少驚訝原則”碴里。學生從校車中下車后,她的名字就從籃球隊的名單中消失了上真,這確實讓人驚訝咬腋。
這里的問題是,校車為傳給構(gòu)造方法的列表創(chuàng)建了別名睡互。正確的做法是根竿,校車自己維護乘客列表。修正的方法很簡單:在 init 中就珠,傳入 passengers 參數(shù)時寇壳,應該把參數(shù)值的副本賦值給self.passengers
修改初始化函數(shù)
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = list(passengers)
在內(nèi)部像這樣處理乘客列表,就不會影響初始化校車時傳入的參數(shù)了妻怎。此外壳炎,這種處理方式還更靈活:現(xiàn)在,傳給 passengers 參數(shù)的值可以是元組或任何其他可迭代對象逼侦,例如set 對象匿辩,甚至數(shù)據(jù)庫查詢結(jié)果,因為 list 構(gòu)造方法接受任何可迭代對象榛丢。自己創(chuàng)建并管理列表可以確保支持所需的.remove() 和 .append() 操作撒汉,這樣 .pick() 和.drop() 方法才能正常運作
除非這個方法確實想修改通過參數(shù)傳入的對象,否則在類中直接把參數(shù)賦值給實例變量之前一定要三思涕滋,因為這樣會為參數(shù)對象創(chuàng)建別名睬辐。如果不確定,那就創(chuàng)建副本