Python3中函數參數傳遞方式實例詳解

這篇文章主要介紹了Python3中函數參數傳遞方式,結合實例形式較爲詳細的分析了Python3中函數參數傳遞的常見操作技巧,需要的朋友可以參考下

本文實例講述了Python3中函數參數傳遞方式。分享給大家供大家參考,具體如下:

之前在看北理工嵩天等老師的python3的課程,在第五週中老師講到了函數的調用傳遞。老師講了這樣一個例子

#處理多個銀行賬戶的餘額信息
def addInterest(balances, rate):
  for i in range(len(balances)):
    balances[i] = balances[i] * (1+rate)
def test():
  amounts = [1000, 105, 3500, 739]
  rate = 0.05
  addInterest(amounts, rate)
  print(amounts)
test()

在這個例子中可以看到爲了處理多個銀行賬戶設置了amounts這個列表,老師的原話是這樣的:

“在test函數中一開始設置了amounts爲4個值的列表,然後將amounts作爲第一個參數傳遞給函數addInterest,並調用執行該函數,最後打印輸出amounts的結果,運行結果如下:

[1050.0,110.25,3675.0,775.95]


然後禮欣老師得出結論,以下爲原話

“在這個例子中可以看到,amounts變量的值好像是被修改了,但是函數是不能改變變量本身即amounts的”

接下來是分析下函數的執行過程,過程如下圖

分析原話如下

“接下來分析下整個執行過程,查看amounts輸出結果是如何發生變化的。首先函數test的前兩條語句建立了兩個變量amounts和rate,然後控制傳遞給addinterest,這裏amounts是一個包含4個整數類型值的列表對象,以實參的形式傳遞給函數addinterest形參balances,下一步執行函數addinterest,從0到length-1範圍執行循環,並更新balances的值。”

重點來了:原話如下

“圖中舊值 [1000, 105, 3500, 739]
並沒有改變,只是Python又創建了一組新值[1050.0,110.25,3675.0,775.95]
,並且使列表對象指向該組新值,而舊值會在Python的垃圾數據回收的時候被清除掉③,從圖中我們可以清楚的看出,爲什麼包含列表參數的程序addinterest修改了列表的值?但程序addinterest結束時存儲在amounts中的是新balances的值,實際上變量amounts從來沒有被改變過。”
“它(amounts,作者注)仍然指向的是調用addinterest函數之前的同一個列表,只是當控制返回到調用函數中時,列表呈現了被修改的狀態”②

最後是得出結論,原話如下:

“通過上述過程我們可以瞭解到:Python的參數是通過值來傳遞的。但是如果變量是可變對象,比如是列表或者是圖形對象,返回到調用程序後,該對象會呈現出被修改的狀態。”

^_^
注:課程原始視頻部分結束。

看了老師的這段講解之後產生了很多疑問:在前面(①處)講的amounts是不能被修改的,但是在(②處)又說列表呈現了被修改的狀態,這不是自相矛盾嗎?在(③)處講列表創建了新值並且使列表指向了新值,這裏不就是說amounts發生了改變嗎?怎麼能說沒變呢?最後結論也是列表呈現出了被修改的狀態。這個結論雲山霧繞,看得人似懂非懂。

那在Python3中參數變量是列表,在調用函數完返回後到底是被修改了還是沒被修改呢?

爲了弄清這個問題,我做了一個實驗,id()可以查看變量在內存中的地址,這個值相當於一個對象的“身份證”。

# 處理多個銀行賬戶的餘額信息
def addInterest(balances, rates):
print()
print("第二處", id(balances))
  for i in range(len(balances)):
    balances[i]= balances[i]*(1+rates)
    print()
    print("第三處",id(balances))
def test():
  amounts = [1000,105,3500,739]
  print()
  print("第一處",id(amounts))
  rate = 0.05
  addInterest(amounts, rate)
  print()
  print(amounts)
  print()
  print("第四處",id(amounts))
test()

輸出結果:

第一處 41203656

第二處 41203656

第三處 41203656

第三處 41203656

第三處 41203656

第三處 41203656

[1050.0, 110.25, 3675.0, 775.95]

第四處 41203656

在這個實驗中可以清楚的看到,amounts這個對象的身份證號碼在整個程序運行過程中從未變過,而非視頻中老師講的創建了新的列表對象。所以amounts作爲一個列表對象在程序運行過程中是被直接修改了,是的就是直接被修改了,而非指向新balances的值。爲什麼可以得出這一結論?我們可以看下第一、三處的id,在未進入函數之前id是41203656(第一處),進入函數之後對象id仍然未變,函數運行完返回之後對象id仍然未變!

所以結論應該這樣寫會比較清楚:

改變參數值值的函數:
實參傳給形參時,python的參數是通過值來傳遞的;
如果變量是可變對象(如列表或者圖形對象),該對象會在函數中會被直接修改,返回到調用程序後也是被修改後的狀態。

那是不是Python3中函數都是像這種傳遞方式呢?我們對課程視頻中的另一個例子做一個簡單的修改。

 # 計算單個銀行賬戶餘額
def addinterest(balance, rate):
  print("第二處", id(balance))
  newBalance = balance * (1 + rate)
  print()
  print("第三處", id(balance))
  print()
  print("第四處", id(newBalance))
  return newBalance
def main():
  amount = 1000
  print("第一處", id(amount))
  print()
  rate = 0.05
  amount = addinterest(amount, rate)
  print()
  print("第五處", id(amount))
  print()
  print(amount)
  print("第六處", id(amount))
main()

運行結果如下:

第一處 33533648

第二處 33533648

第三處 33533648

第四處 33563344

第五處 33563344

1050.0
第六處 33563344

不是說好的直接修改的嗎?怎麼身份證又變了?其實這裏的對象amount是個常數,即爲不可變對象,當在函數中要對對象進行處理時,由於對象不可變,只能新建一個新對象,然後return出新的對象了。

這個也就是目前網絡上大部分博客的結論:

1、不可變對象作爲函數參數,Python通過值傳遞;
2、 可變對象作爲函數參數,Python通過引用傳遞。

注:Python中,數值類型(int和float)、字符串str、元組tuple都是不可變類型。而列表list、字典dict、集合set是可變類型。
(但是也有博客把這兩個結論搞反了)

但是也有博客提出了一個類似這樣的例子

def change(val):
  val = val + [10]
nums = [0, 1]
change(nums)
print(nums)

輸出結果爲

[0, 1]

其實這裏是寫的不嚴謹,不能直接用加號添加列表元素
可以改爲這樣

def change(val):
  newval = [10]
  val= val + newval
nums = [0, 1]
change(nums)
print(nums)

但是輸出結果還是

[0, 1]

難道上面的結論不對嗎?

其實這裏要補充另外一種情況:對於可變對象作爲函數參數,且參數不指向其他對象時,相當於引用傳遞;否則,若參數指向其他對象,則對參數變量的操作並不影響原變量的對象值

函數裏的參數變量val指向了與nums不同的內存變量,所以函數裏的參數變量val不影響原變量nums的值

**這也是因爲python的特性” 變量無類型,對象有類型 “。
變量是對內存空間的引用,當參數變量和原變量指向不同的內存空間時,操作互不影響。**

用下面這個看下

def change(val):
  newval = [10]
  print("第二處",id(val))
  val = val + newval
  print("第三處",id(val))
nums = [0, 1]
print("第一處",id(nums))
change(nums)
print("第四處",id(nums))
print(nums)

運行結果如下:

第一處 39695944
第二處 39695944
第三處 39710024
第四處 39695944
[0, 1]

可以看到第一處的nums和第二處的val的內存地址完全一樣,然後執行到第三處時,由於函數內VAL重新指向了別的內存變量,所以內存地址不同。但是最後結果要輸出變量nums,即第一處第二處內存地址的值,所以和第三處的val就沒關係了。其實這裏的val是沒有返回值的。

想要直接在列表中添加元素可以寫成這樣:

def change(val):
  val.append(10)
nums = [0, 1]
change(nums)
print(nums)

輸出結果是

[0, 1, 10]

關於變量無類型,對象有類型可以這樣理解:只有放在內存空間中的對象(也就是數據)纔有類型,而變量是沒有類型的。

如果還是不明白可以做這樣一種比喻:變量就好比釣魚者,湖水就好像內存,裏面有各種各樣的魚,它們就是對象。釣魚者(變量)的任務就是用某種方式把自己和魚(對象)通過魚線連接起來。那麼,魚(對象)是有類型的,有鰱魚、鯽魚、帶魚。釣魚者(變量)沒有類型,他釣到不同類型的魚(對象)。

用釣魚的比喻解釋下上面的例子

def change(val):
  newval = [10]
  val= val + newval
nums = [0, 1]
change(nums)
print(nums)

1、釣魚人已經釣了一桶魚用nums桶裝着,nums桶可以裝很多魚。

2、現在提着這個nums桶繼續在湖裏釣魚,這時候nums桶暫時叫做裝魚桶val,突然釣魚人釣了一條大魚,發現裝魚桶val裝不下,於是釣魚人又在漁具店買了另一個大的裝魚桶VAL,把大魚和之前的魚一塊裝了。
釣魚活動結束。

3、最後要看看那個叫nums的桶有哪些魚,這時候當然只能看之前的情況。

即這個結論:對於可變對象作爲函數參數,且參數不指向其他對象時,相當於引用傳遞;否則,若參數指向其他對象,則對參數變量的操作並不影響原變量的對象值。

同樣的針對其他兩個結論,也可以用這個比喻解釋:

def change( val):
  newval = val + 10
  return newval
num = 1
num = change(num)
print(num)

1、釣魚人手上的東西num是個小蚯蚓。

2、釣魚人拿着num去湖邊釣魚,小蚯蚓被大魚吃了,釣魚人釣到了一條大魚,釣魚人拿着魚回家。
釣魚活動結束。

3、問釣魚人手上現在拿着什麼東西num?當然是一條大魚。

def change(val):
  val.append(10)
nums = [0, 1]
change(nums)
print(nums)

1、釣魚人提着一個叫nums的桶,桶裏裝着2條魚

2、釣魚人來到湖邊釣魚,此時桶暫時叫裝魚桶val,釣魚人釣到了一條魚放進裝魚桶val。
釣魚活動結束。

3、看看釣魚人桶裏的有幾條魚。

總結來說:

**對於不可變對象作爲函數參數,相當於C系語言的值傳遞;
對於可變對象作爲函數參數,且參數不指向其他對象時,相當於C系語言的引用傳遞。
對於可變對象作爲函數參數,參數指向其他對象,對參數變量的操作不影響原變量的值。**

關於Python相關內容感興趣的讀者可查看本站專題:《Python函數使用技巧總結》、《Python面向對象程序設計入門與進階教程》、《Python數據結構與算法教程》、《Python字符串操作技巧彙總》、《Python編碼操作技巧總結》及《Python入門與進階經典教程

希望本文所述對大家Python程序設計有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章