Python 的函數是怎麼傳遞參數的?
比如這段代碼:
def add(num): num= num+10d=2add(d)print d
輸出 2如果我要像C那樣傳遞d的地址,使之輸出12,我應該怎麼做?
Python 傳參數可以理解為 C 的 const 指針(your_type* const your_variable),它所指向的對象可以被修改產生副作用,但變數本身不能修改指向其他對象。這個和 C++ 的 reference 差不多。所以如果一定要產生 C 的修改指針指向其他對象的效果,用 list、dict 或其他自定義的 mutable 對象包裝是一個辦法,但我認為這樣是一種不良實踐。在 C 語言中用參數輸出結果有非常多的理由:
- C 語言沒有 tuple,不能返回多值,除非聲明一個 struct 類型。這種情況下劃分 in 參數和 out 參數成為一種慣例
- C 語言沒有異常機制,返回值一般要保留給 errno
但這些情況在 Python 中都是不存在的,所以使用非慣例用法會讓人很「驚訝」,然後這種驚訝就是陷阱了 = =~
首先說說Python中參數傳遞的問題,Python中有可變對象(比如列表List)和不可變對象(比如字元串),在參數傳遞時分為兩種情況:
- 對於不可變對象作為函數參數,相當於C系語言的值傳遞;
- 對於可變對象作為函數參數,相當於C系語言的引用傳遞。
我們可以分析下樓主的代碼,參數為不可變對象:
def add(num):
num = num + 10
d = 2
add(d)
print d
理所當然的輸出2,為什麼呢?定義了一個變數d,d指向數字2,然後執行add方法,是複製d到num,剛開始num也指向數字2,在函數體內給num+10,整數是不可變對象,所以,將num+10的結果賦值給num變數,此時num指向num+10也就是12,而d本身並沒有發生改變,仍然指向2。
在Python中,對於不可變對象,調用自身的任意方法,並不會改變對象自身的內容,這些方法會創建新的對象並返回,保證了不可變對象本身是永遠不可變的。
當參數為不可變對象時,這裡我們以列表list舉例說明:如下代碼:def change(num):
num.append(1)
d = [0]
change(d)
print d
上述代碼的輸出結果為 [0,1],現在來分析原因。執行change()方法時,num指向列表[0],因為列表是可變對象,直接作用在原來list上並不會產生新的對象,所以返回[0,1]。
也就是Python在傳遞參數時在可變對象和不可變對象的傳遞上是有區別的。回到樓主的問題:這段代碼def add(num):
num = num + 10
d=2
add(d)
print d
輸出 2
如果我要像C那樣傳遞d的地址,使之輸出12,我應該怎麼做?如上所述,我們知道,對於不可變對象,並不會改變對象自身的值,只會創建新的對象,那麼,我們只需要將新創建的對象利用函數返回到d即可,代碼如下:
def add(num):
num = num + 10
return num
d = 2
d = add(d)
print d
如果是從C++轉來的,那麼就可以理解成Python的傳值方式是按照C++中傳指針的方式傳值的,既不是引用也不是值。如果對象是可變的,那麼操作是在傳入對象上操作的,如果是不可變的,那麼相當於這個標識符指向了另一個對象。其實按照指針的想法來理解就會很簡單。
首先你要明白,Python的函數傳遞方式是賦值,而賦值是通過建立變數與對象的關聯實現的。
對於你的代碼:- 執行 d = 2時,你在__main__里創建了d,並讓它指向2這個整型對象。
- 執行函數add(d)過程中:
- d被傳遞給add()函數後,在函數內部,num也指向了__main__中的2
- 但執行num = num + 10之後,新建了對象12,並讓num指向了這個新對象——12。
- 如果你明白函數中的局部變數與__main__中變數的區別,那麼很顯然,在__main__中,d仍在指著2這個對象,它沒有改變。因此,你列印d時得到了2。
如果你想讓輸出為12,最簡潔的辦法是:
- 在函數add()里增加return num
- 調用函數時使用d = add(d)
def add(num):
num += 10
return num
d = 2
d = add(d)
print d
python 是動態束定機制。同一個存儲對象可以束定到一個或者多個標示符上。而標示符沒有類型聲明,因此也是可以束定到任何可束定體上的(包括函數)。
在調用add(num)時,num被束定到了d的束定對象2上,但由於2是不可覆蓋的對象,所以num+10後創建了一個新的對象12並將其束定到num上。隨著函數調用的結束,num就被銷毀了。而此時d仍然束定在對象2上。@十月碼,iOS Developer的回答很好。也可以用對象class D: def __init__(self,x): self.num=x def add(self): self.num+=10d =D(2)d.add()
可以查看我寫的這篇文章,可以很清楚的解釋python參數傳遞的問題
Python的函數參數傳遞:傳值?引用?最近更新了一些內容,希望更加清楚首先:在Python中所有參數(自變數)在Python里都是按引用傳遞。
數字也是按引用傳遞:
&>&>&> def str_ch(str):... print id(str)&>&>&> a = 1&>&>&> id(a)41668952
&>&>&> str_ch(a)41668952但是對於數字:
&>&>&> a = 1&>&>&> id(a)41668952&>&>&> a += 1&>&>&> id(a)41668928num類型的a的內存地址已經改變(或者說a已經是一個新的對象了)
引起a改變的操作是+=操作,原因是Number數據類型是不允許改變的。而對於list:
&>&>&> b = [1]&>&>&> id(b)140607490691728&>&>&> b.append(2)&>&>&> id(b)140607490691728list類型的b的內存地址沒有發生改變,append()沒有產生新對象
也就是說在函數調用的時候,函數輸出的a是一個新的對象,函數內的操作並沒有影響到原來的a,只是在函數內產生了一個新的對象a,函數內輸出的是新的a,函數外輸出的仍是舊的a;
而傳遞列表的時候,操作改變了列表的內容,沒有改變地址,也沒有產生新的對象,函數內外輸出的都是同一個對象。標題名是函數如何傳遞參數,這也是吸引我來看回答的原因,結果沒找到答案。不過看問題描述,涉及的又非傳遞參數這一問題。
首先說明,這個問題相當複雜,絕非隻言片語即可說清楚,需要綜合變數與對象、可變對象與不可變對象、變數與作用域、參數傳遞的知識。
一、關於函數如何傳遞參數
簡單來說,是隱式傳遞。這裡需要明白變數與對象的關係,即python中的變數均沒有類型,對象才有類型,變數不過是指向對象而已。由於這是基礎也是比較重要的知識,篇幅太長,不作展開。
舉例來說,
&>&>&> def fun(a,b):
... a = 10
... b = 20... print(a,b)...&>&>&> fun(1,2)10 20fun函數中,a,b均是變數,我們用fun(1,2)傳遞參數時,是隱式地傳遞,等同於a = 1, b = 2這樣的一種賦值方式。為何fun(1,2)的最終結果是10,20而不是1,2,可以看成下面的函數
&>&>&> a,b = 1,2
&>&>&> a = 10&>&>&> b = 10&>&>&> print(a,b)10 10當理解了是隱匿傳遞後,我們再來理解下下面這個例子,這裡涉及到可變對象與不可變對象的知識點,數字是不可變對象,即你永遠無法直接在原處修改一個不可變變數。
&>&>&> def fun(a):
... a = 99...&>&>&> b = 10&>&>&> fun(b)&>&>&> print(b)10這裡也可以理解為fun(b)中發生了這些事情,1,a=b=10, a=99,print(b),a和b均指向10這個數字對象,a後來改變所指向的對象,指向了99這個數字對象,b依然指向10這個數字對象。結果顯然應該是10。
二、關於如何全局改變,涉及的是變數作用域的知識,
&>&>&> d = 2
&>&>&> def add():... global d... d += 10...&>&>&> add()&>&>&> print(d)12你舉的這個例子由於涉及到不可變數,所以d的值是無法改變的。如想改變,可在不改變作用域的前提下,使用可變對象,但python不推薦這麼做。如:
&>&>&> def add(num):
... num[0] += 10...&>&>&> d = [2]&>&>&> add(d)&>&>&> print(d)[12]正如前文所說,這個問題還是比較複雜的。傳值方式是:淺拷貝 #一句話的事
@楊可愛 同學的回答很好,但個人認為還應該補充一種情況:
- 對於可變對象作為函數參數,且參數不指向其他對象時,相當於C系語言的引用傳遞;否則,若參數指向其他對象,則對參數變數的操作並不影響原變數的對象值
考慮如下情況:
def change(newVar):
newVar = [changedValue]
print("newVar ="+str(newVar))
myVar = [originalValue]
change(myVar)
print("myVar ="+str(myVar))
&>&>&>&>&>&>&>&>&>&>&>&>&>&>
newVar =[changedValue]
myVar =[originalValue]
參數變數newVar指向了新的對象[changedValue],而原變數myVar的值仍為[originalValue]。因為newVar指向了與myVar不同的內存變數,所以對參數變數newVar的操作不影響原變數myVar的值。
可以使用圖表理解:
調用change(myVar)前,myVar指向內存區域Memory_1,該區域值為originalValue:
調用change(myVar)後,myVar指向內存區域仍為Memory_1,該區域值仍為originalValue;newVar指向內存區域Memory_2,該區域值為changedValue:
因為參數變數(newVar)指向了新建的內存空間(Memory_2),所以對參數變數(newVar)的修改([changedValue])不影響原變數(myVar)的指向的內存空間(Memory_1)的值([originalValue])。
這也是因為python的特性" 變數無類型,對象有類型 "。
變數是對內存空間的引用,當參數變數和原變數指向不同的內存空間時,操作互不影響。
總結來說:
- 對於不可變對象作為函數參數,相當於C系語言的值傳遞;
- 對於可變對象作為函數參數,且參數不指向其他對象時,相當於C系語言的引用傳遞。
- 對於可變對象作為函數參數,參數指向其他對象,對參數變數的操作不影響原變數的值。
可變對象list,dic都是傳址,不可變對象都是傳值,這樣理解,有毛病嗎?
原話:儘管Python不支持通過引用進行參數傳遞,但通常能通過返回元組,並將結果複製給最初的調用者的參數名來進行模擬
def add(num):
num+=10
return num
d=2
d=add(d)
print d
用函數除了實現功能獨立,還為了重用它。
如果它的參數和你的外部變數相關,那麼如何重用它呢?我一般是把這個d放到一個list裡面,即[d], 然後在函數裡面修改list[0]~
推薦閱讀:
※*吧上有海外留學生問全部用遞歸求第N個質數,不能用循環
※Python中 __init__的通俗解釋?
※從源碼編譯 Python
※Linux線程數限制
TAG:Python |