原來Python呼叫函數時引數的傳遞方式是call by assignment

更新於 發佈於 閱讀時間約 6 分鐘

花了些時間,靜下心來,仔仔細細地研究了一番,總算把Python呼叫函數時引數的傳遞方式給徹底搞清楚了。

為什麼要去計較呼叫函數時,引數到底是怎麼傳遞的呢?這是因為擔心原本好好的變數,在充當引數進入函數之後,會被徹底改造,完全失去原本的面貌;如果不察,錯把馮京當馬涼,以為變數還是原來的樣貌,那可是會出亂子的。例如,假設有這麼個函數

def f(x):
x = new_value

那執行

a = value
f(a)

之後,這個a的值會是什麼呢?是原來的value嗎?還是變成了new_value了?這個如果不搞清楚,那又怎麼能確保後續的程式所得到的結果是正確的呢?!

網路上許多文章在討論函數引數的傳遞方式時,都會從call by value和call by reference這兩個方式談起,然後再談到call by sharing這個方式。以前面f(x)這個函數的例子來看,call by value和call by reference這兩種方式剛好會有相反的結果;一個a的值還是原來的value,另一個則是會變成new_value。但是很不幸的,這兩種方式都不是Python所採用的方式。

有三種方式,其中兩種沒有被採用,那Python用的是call by sharing囉?是有這麼一說,在〈wikipedia: Evaluation strategy〉這篇文章中,就是把Python歸類在使用call by sharing的程式語言類別中;不過在該文中卻也指出,「call by sharing」並非通用的術語,不同的程式語言對這個術語的詮釋方式,並不一致。〈傳值?傳參?〉這篇文章也持相同的觀點。事實上,儘管在Python的官網中找了老半天,也沒看到關於call by sharing這幾個字的任何蛛絲馬跡。

這也不是,那也不是,那Python函數的引數,究竟是用什麼方式傳遞的呢?答案是:call by assignment。在官網的Programming FAQ中有這麼個問題:

How do I write a function with output parameters (call by reference)?

在解答中提到:

Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per se.

所以Python用的是call by assignment無誤。

Python用的是call by assignment,然後呢?當變數充當引數進入函數,在執行完函數之後,這個變數到底有沒有可能會被徹底改造,完全失去原本的面貌?在回答這個問題之前,要先來看看幾段程式,複習一下關於指定運算子(assignment operator),也就是「=」的運作方式。

程式1

a = 8  # 把8指定給a
x = a # 把指定給a的值也指定給x
x = 3 # 讓原本指定給x的值和x脫鉤,並將3指定給x
print(a) # 會印出8

程式2

a = [1, 2, 3]  # 把[1, 2, 3]指定給a
x = a # 把指定給a的值也指定給x
x = [5, 7] # 讓原本指定給x的值和x脫鉤,並將[5, 7]指定給x
print(a) # 會印出[1, 2, 3]

程式3

a = [1, 2, 3]  # 把[1, 2, 3]指定給a
x = a # 把指定給a的值也指定給x
x[1] = 9 # 修改指定給x的值,把索引值為1的元素改為9
print(a) # 會印出[1, 9, 3]

指定運算子的運作方式,尤其是程式3,是最容易讓人產生混淆的地方;這個部分沒徹底搞清楚,就不算是真正了解指定運算子的運作方式。

搞清楚指定運算子的運作方式之後,要了解call by assignment是怎麼一回事,那就容易多了。來看看下面這幾段程式:

程式4

def f(x):
x = 3 # 把3指定給x

a = 8 # 把8指定給a
f(a) # 把指定給a的值指定給函數的參數,然後執行函數
print(a) # 會印出8

程式5

def f(x):
x = [5, 7] # 把[5, 7]指定給x

a = [1, 2, 3] # 把[1, 2, 3]指定給a
f(a) # 把指定給a的值指定給函數的參數,然後執行函數
print(a) # 會印出[1, 2, 3]

程式6

def f(x):
x[1] = 9 # 修改指定給x的值,把索引值為1的元素改為9

a = [1, 2, 3] # 把[1, 2, 3]指定給a
f(a) # 把指定給a的值指定給函數的參數,然後執行函數
print(a) # 會印出[1, 9, 3]

程式4、5、6其實挺簡單的,就只是把程式1、2、3中的第2、3行改成用函數來做而已。這幾段程式對照著看,應該就可以知道call by assignment是怎麼回事了。

等等!故事還沒結束!如果把程式4、5、6中的變數a給改成x,那結果還是一樣嗎?例如,把程式4改成

def f(x):
x = 3

x = 8
f(x)
print(x)

這時候還是會印出8嗎?答案是:是的,還是會印出8。這是因為雖然在f(x)中把3給指定給x,但這時候這個x已經擺脫過去的羈絆,成為全新的、只屬於f(x)x;它已經變成是函數內部的區域變數,再也跟函數外面的x沒有任何關聯了。程式5的情況也一樣,在函數內部的x被指定新的值[5, 7]之後,它也變成是區域變數,與函數外面的x沒有一丁點關係。至於程式6,因為x在函數內並沒有被指定新的值,所以它還是和原來的值綁在一起,修改某個元素的值,函數外頭的那個x也會跟著受影響。

針對這個主題,下面這兩篇文章有非常精闢與詳盡的說明,頗值得一讀:

留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
15會員
142內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2024/05/08
呼!折騰了好久,終於徹底搞清楚pygame的各個blend mode所用的計算式,到底是長啥樣子了。
2024/05/08
呼!折騰了好久,終於徹底搞清楚pygame的各個blend mode所用的計算式,到底是長啥樣子了。
2023/12/20
在寫《The Nature of Code閱讀心得筆記——使用Python實作》的[第四章]4.3節時,原書提到,在使用Java的ArrayList時,如果用迴圈一面走訪一面又移除其中的元素,那會有難以察覺的問題存在。寫個小程式測試的結果發現,Python的list也會有一樣的問題。
Thumbnail
2023/12/20
在寫《The Nature of Code閱讀心得筆記——使用Python實作》的[第四章]4.3節時,原書提到,在使用Java的ArrayList時,如果用迴圈一面走訪一面又移除其中的元素,那會有難以察覺的問題存在。寫個小程式測試的結果發現,Python的list也會有一樣的問題。
Thumbnail
2023/10/15
用matplotlib畫正弦函數sin的圖形會有多難呢?應該是挺簡單的。不過,要畫得漂亮讓人滿意,還是非得費一番功夫調整不可。
Thumbnail
2023/10/15
用matplotlib畫正弦函數sin的圖形會有多難呢?應該是挺簡單的。不過,要畫得漂亮讓人滿意,還是非得費一番功夫調整不可。
Thumbnail
看更多
你可能也想看
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
在 Python 中,print( ) 函數用於將結果輸出到螢幕上。當你嘗試將不同資料型別(例如字串和數字)混合在一起輸出時,print( )函數無法直接處理這些不同型別的資料,因此你需要先將它們轉換為相同的資料型別。通常,這意味著需要將數字轉換為字串型別,以便與其他字串一同輸出。 雖然我們也可以
Thumbnail
在 Python 中,print( ) 函數用於將結果輸出到螢幕上。當你嘗試將不同資料型別(例如字串和數字)混合在一起輸出時,print( )函數無法直接處理這些不同型別的資料,因此你需要先將它們轉換為相同的資料型別。通常,這意味著需要將數字轉換為字串型別,以便與其他字串一同輸出。 雖然我們也可以
Thumbnail
在這一章中,我們探討了 PHP 中的函數,包括函數的基本結構、不同的函數定義方式(如函數聲明、函數表達式、箭頭函數和匿名函數)以及如何呼叫函數。我們還討論了函數的參數處理方式,包括單個參數、多個參數、預設參數值和剩餘參數。此外,我們還介紹了函數的返回值,包括返回單個值、返回物件和返回函數的情況。
Thumbnail
在這一章中,我們探討了 PHP 中的函數,包括函數的基本結構、不同的函數定義方式(如函數聲明、函數表達式、箭頭函數和匿名函數)以及如何呼叫函數。我們還討論了函數的參數處理方式,包括單個參數、多個參數、預設參數值和剩餘參數。此外,我們還介紹了函數的返回值,包括返回單個值、返回物件和返回函數的情況。
Thumbnail
呈上次使用logging來撰寫日誌,利用類別包裝的方式,可實現多個日誌紀錄器,但發現這樣就失去它原先,可以回傳是誰呼叫他並記錄行數的功能。 [Python]使用logging創建兩個以上的日誌紀錄 若開啟函式名稱、行數及訊息的功能,就會像這樣,幾乎都是記錄到,我定義中類別的函式
Thumbnail
呈上次使用logging來撰寫日誌,利用類別包裝的方式,可實現多個日誌紀錄器,但發現這樣就失去它原先,可以回傳是誰呼叫他並記錄行數的功能。 [Python]使用logging創建兩個以上的日誌紀錄 若開啟函式名稱、行數及訊息的功能,就會像這樣,幾乎都是記錄到,我定義中類別的函式
Thumbnail
在Python中,我們可以用def關鍵字定義函數,並透過函數名稱呼叫它。函數參數可以是必填、關鍵字、默認或不定長度的類型。return語句負責結束函數並回傳值。全域變數可以在整個程序中使用,而區域變數只能在特定函數內使用。我們還可以在一個文件中定義函數,然後在另一個文件中呼叫它。
Thumbnail
在Python中,我們可以用def關鍵字定義函數,並透過函數名稱呼叫它。函數參數可以是必填、關鍵字、默認或不定長度的類型。return語句負責結束函數並回傳值。全域變數可以在整個程序中使用,而區域變數只能在特定函數內使用。我們還可以在一個文件中定義函數,然後在另一個文件中呼叫它。
Thumbnail
在Python中,import是一個關鍵字,用於將其他模組或套件中的程式碼引入到當前的程式中以供使用。 這個關鍵字允許你在你的程式中使用其他地方定義的變數、函式和類等。 當你使用import時,Python會搜索指定模組或套件的位置,並將其中的程式碼載入到你的程式中,這樣你就可以在程式中使用它們
Thumbnail
在Python中,import是一個關鍵字,用於將其他模組或套件中的程式碼引入到當前的程式中以供使用。 這個關鍵字允許你在你的程式中使用其他地方定義的變數、函式和類等。 當你使用import時,Python會搜索指定模組或套件的位置,並將其中的程式碼載入到你的程式中,這樣你就可以在程式中使用它們
Thumbnail
今天來介紹python的函式 函式在python中是非常重要的一環,因為到了後期,程式會越來越複雜。 而函式可以想成是容易管理的小程式,當我們需要使用時,只需呼叫即可。
Thumbnail
今天來介紹python的函式 函式在python中是非常重要的一環,因為到了後期,程式會越來越複雜。 而函式可以想成是容易管理的小程式,當我們需要使用時,只需呼叫即可。
Thumbnail
在Python函式中,可以使用None來指定動態的預設引數,使用更靈活,Docstrings同時能夠提供清晰的文檔。本篇文章說明利用這種方式來讓函式更彈性,輸入不同型態的關鍵字引數,並在事件紀錄時間的Log訊息的範例中詮釋,可自行輸入時間或者預設導入系統的時間,在跟其他程式交握時,可以更靈活的應用。
Thumbnail
在Python函式中,可以使用None來指定動態的預設引數,使用更靈活,Docstrings同時能夠提供清晰的文檔。本篇文章說明利用這種方式來讓函式更彈性,輸入不同型態的關鍵字引數,並在事件紀錄時間的Log訊息的範例中詮釋,可自行輸入時間或者預設導入系統的時間,在跟其他程式交握時,可以更靈活的應用。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News