歡迎關注公眾號:python資料科學家
【要點搶先看】
1.函式引數傳遞的實現過程
2.可變物件和不可變物件引數傳遞、修改的區別
3.如何避免不可變物件引數傳遞的本地修改
今天我們再來說說函式中的引數傳遞問題
【妹子說】這個看上去自然而然的過程裡有什麼講究麼?
有很多需要注意的地方,從這一節開始就來仔細的聊聊。
引數的傳遞是通過自動將物件賦值給本地變數名來實現的。在函式執行時,函式頭部的引數名是一個新的、本地的變數名,這個變數名是在函式的本地作用域記憶體在。引數的傳遞本質上就是python賦值的另一個例項而已。
那麼,這個問題分為可變物件和不可變物件兩種情況進行討論:
在原處改變函式的可變物件引數的值會對呼叫者有影響。函式能夠就地改變傳入的可變物件,因此其結果會影響呼叫者,這其實和前面介紹過的物件賦值原理是一樣的;
而不可變物件的引用重新賦值會指向新的物件,因此不會改變呼叫者。
先看一個不可變物件的例子
def f(a):
a = 99
print(a)
b = 88
f(b)
print(b)
99
88
複製程式碼
在函式中修改a對於呼叫函式的地方沒有任何影響,因為他在函式內部直接把本地變數a重置為了一個完全不同的新物件,所以他不會影響最初的變數b
而當引數傳遞像列表和字典這樣的可修改物件的時候,我們還需要注意,對這樣的可變物件的原處修改可能在函式退出後依然有效,並由此影響到呼叫者。
def change(a,b):
a = 2
b[0] = 'spam'
x = 1
l = [1,2]
change(x,l)
print(x,l)
1 ['spam', 2]
複製程式碼
再次對比可以看出,呼叫者的不可變變數x沒有受到影響,而可變變數L在函式內部進行本地修改,並影響到了自身
可以看出,對於引數a,僅僅把本地變數a修改為引用一個完全不同的物件,並沒有改變呼叫者作用域中的名稱x的繫結。而引數b被傳給了一個可變物件(在呼叫者作用域中叫做L的列表),因為第二個賦值是一個在原處發生的物件改變,對函式中b[0]進行賦值的結果會在函式返回後影響L的值,他修改了b所引用的物件的一部分,因為引用共享物件的緣故,L也被同時改變。
再強調一次,其實引數傳遞後的本地修改過程和簡單物件賦值後的物件修改,實質上是一回事,換句話說就等於下面這個例子所描述的程式過程
L = [1,2]
b = L
b[0] = 'spam'
print(L)
['spam', 2]
複製程式碼
【妹子說】那如何避免對可變引數的修改呢?
實際上,可變引數的原處修改行為並不是一個bug,它只是引數傳遞在python中工作的方式。在python中,預設通過引用進行函式的引數傳遞,是因為這通常是我們所想要的:這意味著不需要建立多個拷貝就可以在我們的程式中傳遞很大的物件。如果不想要函式內部在原處的修改影響傳遞給它的物件,那麼,我們可以簡單的建立一個明確的可變物件的拷貝
def change(a,b):
a = 2
b[0] = 'spam'
x = 1
l = [1,2]
change(x,l[:])
print(x,l)
1 [1, 2]
複製程式碼
或者在函式內部進行拷貝,這樣可以不改變傳入的物件,函式呼叫看上去沒有變化
def change(a,b):
b = b[:]
a = 2
b[0] = 'spam'
x = 1
l = [1,2]
change(x,l)
print(x,l)
1 [1, 2]
複製程式碼
今天就說這麼多,看上去不是很複雜,不過在程式中可是經常會碰到的,可不能掉以輕心。
公眾號二維碼:python資料科學家: