學習用 Python 程式設計時要避免的 3 個錯誤

發表於2017-08-21

這些錯誤會造成很麻煩的問題,需要數小時才能解決。

當你做錯事時,承認錯誤並不是一件容易的事,但是犯錯是任何學習過程中的一部分,無論是學習走路,還是學習一種新的程式語言都是這樣,比如學習 Python。

為了讓初學 Python 的程式設計師避免犯同樣的錯誤,以下列出了我學習 Python 時犯的三種錯誤。這些錯誤要麼是我長期以來經常犯的,要麼是造成了需要幾個小時解決的麻煩。

年輕的程式設計師們可要注意了,這些錯誤是會浪費一下午的!

1、 可變資料型別作為函式定義中的預設引數

這似乎是對的?你寫了一個小函式,比如,搜尋當前頁面上的連結,並可選將其附加到另一個提供的列表中。

從表面看,這像是十分正常的 Python 程式碼,事實上它也是,而且是可以執行的。但是,這裡有個問題。如果我們給 add_to 引數提供了一個列表,它將按照我們預期的那樣工作。但是,如果我們讓它使用預設值,就會出現一些神奇的事情。

試試下面的程式碼:

可能你認為我們將看到:

但實際上,我們看到的卻是:

為什麼呢?如你所見,每次都使用的是同一個列表,輸出為什麼會是這樣?在 Python 中,當我們編寫這樣的函式時,這個列表被例項化為函式定義的一部分。當函式執行時,它並不是每次都被例項化。這意味著,這個函式會一直使用完全一樣的列表物件,除非我們提供一個新的物件:

答案正如我們所想的那樣。要想得到這種結果,正確的方法是:

或是在第一個例子中:

這將在模組載入的時候移走例項化的內容,以便每次執行函式時都會發生列表例項化。請注意,對於不可變資料型別,比如元組字串整型,是不需要考慮這種情況的。這意味著,像下面這樣的程式碼是非常可行的:

2、 可變資料型別作為類變數

這和上面提到的最後一個錯誤很相像。思考以下程式碼:

這段程式碼看起來非常正常。我們有一個儲存 URL 的物件。當我們呼叫 add_url 方法時,它會新增一個給定的 URL 到儲存中。看起來非常正確吧?讓我們看看實際是怎樣的:

b.urls:

a.urls:

等等,怎麼回事?!我們想的不是這樣啊。我們例項化了兩個單獨的物件 ab。把一個 URL 給了 a,另一個給了 b。這兩個物件怎麼會都有這兩個 URL 呢?

這和第一個錯例是同樣的問題。建立類定義時,URL 列表將被例項化。該類所有的例項使用相同的列表。在有些時候這種情況是有用的,但大多數時候你並不想這樣做。你希望每個物件有一個單獨的儲存。為此,我們修改程式碼為:

現在,當建立物件時,URL 列表被例項化。當我們例項化兩個單獨的物件時,它們將分別使用兩個單獨的列表。

3、 可變的分配錯誤

這個問題困擾了我一段時間。讓我們做出一些改變,並使用另一種可變資料型別 – 字典

現在,假設我們想把這個字典用在別的地方,且保持它的初始資料完整。

簡單吧?

現在,讓我們看看原來那個我們不想改變的字典 a

哇等一下,我們再看看 b

等等,什麼?有點亂……讓我們回想一下,看看其它不可變型別在這種情況下會發生什麼,例如一個元組

現在 c (2, 3),而 d (4, 5)

這個函式結果如我們所料。那麼,在之前的例子中到底發生了什麼?當使用可變型別時,其行為有點像 C 語言的一個指標。在上面的程式碼中,我們令 b = a,我們真正表達的意思是:b 成為 a 的一個引用。它們都指向 Python 記憶體中的同一個物件。聽起來有些熟悉?那是因為這個問題與先前的相似。其實,這篇文章應該被稱為「可變引發的麻煩」。

列表也會發生同樣的事嗎?是的。那麼我們如何解決呢?這必須非常小心。如果我們真的需要複製一個列表進行處理,我們可以這樣做:

這將遍歷並複製列表中的每個物件的引用,並且把它放在一個新的列表中。但是要注意:如果列表中的每個物件都是可變的,我們將再次獲得它們的引用,而不是完整的副本。

假設在一張紙上列清單。在原來的例子中相當於,A 某和 B 某正在看著同一張紙。如果有個人修改了這個清單,兩個人都將看到相同的變化。當我們複製引用時,每個人現在有了他們自己的清單。但是,我們假設這個清單包括尋找食物的地方。如果“冰箱”是列表中的第一個,即使它被複制,兩個列表中的條目也都指向同一個冰箱。所以,如果冰箱被 A 修改,吃掉了裡面的大蛋糕,B 也將看到這個蛋糕的消失。這裡沒有簡單的方法解決它。只要你記住它,並編寫程式碼的時候,使用不會造成這個問題的方式。

字典以相同的方式工作,並且你可以通過以下方式建立一個昂貴副本:

再次說明,這隻會建立一個新的字典,指向原來存在的相同的條目。因此,如果我們有兩個相同的列表,並且我們修改字典 a 的一個鍵指向的可變物件,那麼在字典 b 中也將看到這些變化。

可變資料型別的麻煩也是它們強大的地方。以上都不是實際中的問題;它們是一些要注意防止出現的問題。在第三個專案中使用昂貴複製操作作為解決方案在 99% 的時候是沒有必要的。你的程式或許應該被改改,所以在第一個例子中,這些副本甚至是不需要的。

程式設計快樂!在評論中可以隨時提問。


作者簡介:

Pete Savage – Peter 是一位充滿激情的開源愛好者,在過去十年裡一直在推廣和使用開源產品。他從 Ubuntu 社群開始,在許多不同的領域自願參與音訊製作領域的研究工作。在職業經歷方面,他起初作為公司的系統管理員,大部分時間在管理和建立資料中心,之後在 Red Hat 擔任 CloudForms 產品的主要測試工程師。

 

相關文章