def func(ls=[]):
ls.append(1)
return ls
a = func()
print(a) ### [1]
b = func()
print(a,b) ### [1, 1] [1, 1]
uj5u.com熱心網友回復:
很有意思的問題。我稍微研究了一下,感覺跟Python的記憶體管理有關。首先看這一段代碼:
>>> id([])
2785606466440
>>> l = []
>>> id(l)
2785606466440
>>> l.append(2)
>>> id(l)
2785606466440
>>> id([])
2785609579656
注意[]創建了一個新的空list,當對其進行操作(比如append)時,其在記憶體中的位置并沒發生變化(至少當插入量很小時不發生變化)。反過來看我們定義的函式的函式頭。
def func(ls=[])
記得Python的物件本質上都是參考(類似于智能指標)。這個函式頭說的是,函式內變數ls所參考的list位置由用戶給出。如果未給出,默認指向當前[]所在的記憶體位置。
那么[]的當前位置是何時確定的呢?是在每次執行這個函式呼叫(比如a = func()或者b = func())的時候分別確定的嗎?如果是這樣,就不會有這么奇葩的結果。其實,ls變數的默認值應該是在第一次運行這個.py檔案,Python解釋器全文掃描的時候確定的,有點像C語言的編譯時確定的那些宏名稱。我們假設代碼剛運行到函式定義這一段時,[]對應的記憶體地址是2785606466440。這是解釋器就會記住,ls默認指向記憶體2785606466440處存放的那個list物件。之后的操作也就是對這個物件的操作。記住,這是在Python運行函式定義部分代碼的時候確定,而非運行函式呼叫代碼的時候確定的。當運行下一行
a = func()的時候,Python發現用戶未指定ls引數,因此按照上面邏輯,ls指向2785606466440處的記憶體,并對那個list物件進行了append操作。同樣,執行到
b = func()時,ls也被指向了2785606466440處的list物件,即物件a。后面的結果就容易理解了。
為了驗證這一猜測,我們可以在func函式里加一行print指令顯示ls物件的id(順便說一下,我們平時用的CPython對id函式的實作正是回傳物件的記憶體首地址)。
def func(ls=[]):
print("id(ls) =", id(ls) # 顯示ls變數的記憶體地址
ls.append(1)
return ls
再做上面的測驗,應該就清楚了。
另一方面,我也同意,這段代碼很直白地揭示了Python解釋邏輯里很反直覺的一點。說它是Python的一個bug可能有點過分,不過的確很難讓人接受。
uj5u.com熱心網友回復:
簡單說下上面你寫的代碼吧:1、func函式:func函式需要一個引數——ls串列,其默認值是空串列,如果沒有提供ls引數而且系統也沒有ls串列,那么就創建一個ls空串列。然后給ls串列添加元素1,然后回傳ls串列。
2、a = func():第一次呼叫func函式,因為系統之前沒有ls串列,所以會使用默認值來創建一個空的ls串列,當呼叫func函式后,ls就從[]變成[1],然后把這個結果直接賦值給變數a,所以a是[1]。
3、b = func():第二次呼叫func函式,此時系統內已經有了一個ls串列,其值為[1],當再一次呼叫func函式,ls就從[1]變成[1,1],然后把這個結果直接賦值給變數b,所以b是[1,1]。
4、那為什么此時a也是[1,1]呢,這是因為python中的賦值只是參考,在這個例子里,變數a和b都是指向ls串列,他們的值都來源于ls串列,當ls串列發生變化,變數a的值也發生變化,因此a也是[1,1]了。我估計你不太明白的點就在這里,建議你再學習下Python 直接賦值、淺拷貝和深度拷貝。
uj5u.com熱心網友回復:
這個是非常典型的錯誤示范,很多資料都有講uj5u.com熱心網友回復:
可變的資料型別是作用范圍是全域uj5u.com熱心網友回復:
就是變數的作用域,函式引數傳遞,傳值傳址,淺拷貝深拷貝。在Python中,串列元組字典都屬于物件,函式傳遞時,都是使用傳遞物件的地址給了引數。轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/126294.html
