天天看點

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

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

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

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

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

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

1、 可變資料類型作為函數定義中的預設參數

這似乎是對的?你寫了一個小函數,比如,搜尋目前頁面上的連結,并可選将其附加到另一個提供的清單中。

def search_for_links(page, add_to=[]): 

    new_links = page.search_for_links() 

    add_to.extend(new_links) 

    return add_to 

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

試試下面的代碼:

def fn(var1, var2=[]): 

    var2.append(var1) 

    print var2 

fn(3) 

fn(4) 

fn(5) 

可能你認為我們将看到:

[3] 

[4] 

[5] 

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

[3, 4] 

[3, 4, 5] 

為什麼呢?如你所見,每次都使用的是同一個清單,輸出為什麼會是這樣?在 python

中,當我們編寫這樣的函數時,這個清單被執行個體化為函數定義的一部分。當函數運作時,它并不是每次都被執行個體化。這意味着,這個函數會一直使用完全一樣的清單對象,除非我們提供一個新的對象:

fn(3, [4])  

[4, 3] 

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

def fn(var1, var2=none): 

    if not var2: 

        var2 = [] 

或是在第一個例子中:

def search_for_links(page, add_to=none): 

    if not add_to: 

        add_to = [] 

這将在子產品加載的時候移走執行個體化的内容,以便每次運作函數時都會發生清單執行個體化。請注意,對于不可變資料類型,比如元組、字元串、整型,是不需要考慮這種情況的。這意味着,像下面這樣的代碼是非常可行的:

class urlcatcher(object): 

    urls = [] 

    def add_url(self, url): 

        self.urls.append(url) 

2、 可變資料類型作為類變量

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

a = urlcatcher() 

a.add_url('http://www.google.com') 

b = urlcatcher() 

b.add_url('http://www.bbc.co.hk') 

這段代碼看起來非常正常。我們有一個儲存 url 的對象。當我們調用 add_url 方法時,它會添加一個給定的 url 到存儲中。看起來非常正确吧?讓我們看看實際是怎樣的:

a = urlcatcher()a.add_url('http://www.google.com')b = urlcatcher()b.add_url('http://www.bbc.co.hk')

b.urls:

['http://www.google.com', 'http://www.bbc.co.uk'] 

a.urls:

等等,怎麼回事?!我們想的不是這樣啊。我們執行個體化了兩個單獨的對象 a 和 b。把一個 url 給了 a,另一個給了 b。這兩個對象怎麼會都有這兩個 url 呢?

這和第一個錯例是同樣的問題。建立類定義時,url 清單将被執行個體化。該類所有的執行個體使用相同的清單。在有些時候這種情況是有用的,但大多數時候你并不想這樣做。你希望每個對象有一個單獨的儲存。為此,我們修改代碼為:

    def __init__(self): 

        self.urls = [] 

現在,當建立對象時,url 清單被執行個體化。當我們執行個體化兩個單獨的對象時,它們将分别使用兩個單獨的清單。

3、 可變的配置設定錯誤

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

a = {'1': "one", '2': 'two'} 

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

b = a 

b['3'] = 'three' 

簡單吧?

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

{'1': "one", '2': 'two', '3': 'three'} 

哇等一下,我們再看看 b?

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

c = (2, 3) 

d = c 

d = (4, 5) 

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

這個函數結果如我們所料。那麼,在之前的例子中到底發生了什麼?當使用可變類型時,其行為有點像 c 語言的一個指針。在上面的代碼中,我們令 b

= a,我們真正表達的意思是:b 成為 a 的一個引用。它們都指向 python

記憶體中的同一個對象。聽起來有些熟悉?那是因為這個問題與先前的相似。其實,這篇文章應該被稱為「可變引發的麻煩」。

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

b = a[:] 

這将周遊并複制清單中的每個對象的引用,并且把它放在一個新的清單中。但是要注意:如果清單中的每個對象都是可變的,我們将再次獲得它們的引用,而不是完整的副本。

假設在一張紙上列清單。在原來的例子中相當于,a 某和 b

某正在看着同一張紙。如果有個人修改了這個清單,兩個人都将看到相同的變化。當我們複制引用時,每個人現在有了他們自己的清單。但是,我們假設這個清單包括尋找食物的地方。如果“冰箱”是清單中的第一個,即使它被複制,兩個清單中的條目也都指向同一個冰箱。是以,如果冰箱被

a 修改,吃掉了裡面的大蛋糕,b

也将看到這個蛋糕的消失。這裡沒有簡單的方法解決它。隻要你記住它,并編寫代碼的時候,使用不會造成這個問題的方式。

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

b = a.copy() 

再次說明,這隻會建立一個新的字典,指向原來存在的相同的條目。是以,如果我們有兩個相同的清單,并且我們修改字典 a 的一個鍵指向的可變對象,那麼在字典 b 中也将看到這些變化。

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

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

作者簡介:

pete savage - peter 是一位充滿激情的開源愛好者,在過去十年裡一直在推廣和使用開源産品。他從 ubuntu

社群開始,在許多不同的領域自願參與音頻制作領域的研究工作。在職業經曆方面,他起初作為公司的系統管理者,大部分時間在管理和建立資料中心,之後在

red hat 擔任 cloudforms 産品的主要測試工程師。

作者:佚名

來源:51cto