把陷阱去掉了,反倒踩進了新的陷阱?
相信很多人都知道,Python有一個默認參數陷阱。函數的默認參數不能使用可變類型,否則會導致運行結果跟你想的不一樣。例如:
這段代碼運行的時候,如果傳入了一個列表,那么就往列表里面添加青南和產品經理?并用逗號連接起來打印。如果沒有傳入參數,就打印青南,產品經理。看起來似乎沒有問題。但如果你不帶參數多運行幾次,就會發現問題出來了:
為什么每次不傳入參數的時候,打印的結果都不一樣?而且越來越長?這個原因我公眾號以前已經講過了,根本原因就在于默認參數user_list=[]?這里的默認值[]是在代碼運行時(Runtime)啟動的時候就初始化的,每次調用函數一直使用這同一個對象,并不是每次調用函數的時候初始化。
要解決這個問題也非常簡單,默認參數使用不可變對象就可以了:
最近,我在上古代碼中開發新功能,看到有一段處理Exception的函數,默認參數就使用的字典。代碼大概長成下面這樣:
于是我就順手把它改了:
理論上講,我這樣改移除了一個隱患,并且對后面的具體代碼來說,param_dict始終都是一個字典,應該沒有什么問題才對。
結果不久以后,有人給我報Bug。我一看,不就是我改的這個函數報錯了嗎。一通分析函數調用棧,發現了問題的原因。
這個函數原來是這樣寫的:
而上古代碼里面,調用這個函數的時候,有下面兩種寫法:
當他用不到param_dict?參數的時候,他竟然主動傳了個None?進去。這樣一來,他傳入的None?就會被我強制轉換為空字典。于是代碼就會走到extra_msg.format(**param_dict)里面。這個時候由于沒有填充大括號中的參數,于是就報錯了:
這個新的bug解決起來也簡單,再判斷一下param_dict是不是空就可以了:
這真的應驗了那句話,當一段顯然有問題的代碼竟然正常運行的時候,你就不要去動他了,它可能處于負負得正的狀態,這一改反而可能把它改錯了。