首頁  >  文章  >  後端開發  >  使用Python變數易錯點介紹

使用Python變數易錯點介紹

巴扎黑
巴扎黑原創
2017-09-19 09:45:121818瀏覽

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]
[3,4]
[3,4,5]

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

fn(3,[4])
[4,3]

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

def fn(var1, var2=None):
    ifnot var2:
        var2 =[]
    var2.append(var1)

或是在第一個例子中:

def search_for_links(page, add_to=None):
    ifnot add_to:
        add_to =[]
    new_links = page.search_for_links()
    add_to.extend(new_links)
    return add_to

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

def func(message="my message"):
    print(message)

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

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

class URLCatcher(object):
    urls =[]
    def add_url(self, url):
        self.urls.append(url)

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

a =URLCatcher()
a.add_url('http://www.google.com')
b =URLCatcher()
b.add_url('http://www.pythontab.com')
print(b.urls)
print(a.urls)

結果:

['http://www.google.com','http://www.pythontab.com']
['http://www.google.com','http://www.pythontab.com']

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

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

class URLCatcher(object):
    def __init__(self):
        self.urls =[]
    def add_url(self, url):
        self.urls.append(url)

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

3、可變的分配錯誤

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

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

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

b = a
b['3']='three'

簡單吧?

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

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

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

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

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

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% 的时候是没有必要的。

以上是使用Python變數易錯點介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn