不要使用可變類型作為參數(shù)的默認值

先看一個例子

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 違反了設(shè)計接口的最佳實踐,即“最少驚訝原則”。學生從校車中下車后,她的名字就從籃球隊的名單中消失了,這確實讓人驚訝。
這里的問題是,校車為傳給構(gòu)造方法的列表創(chuàng)建了別名。正確的做法是,校車自己維護乘客列表。修正的方法很簡單:在 init 中,傳入 passengers 參數(shù)時,應(yīng)該把參數(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)建副本

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容