Python 函式的參數傳遞方式:Passed by assignment

典型的參數傳遞方式有二種:

  • Pass-by-value: 複製參數的值傳入,所以原參數內容不會被影響。
  • Pass-by-reference: 傳入參數的參考,會影響原參數內容。

還有少數程式語言使用以下兩種傳遞方式:

  • Pass-by-name: 將傳入的參數視為變數名稱或是字串,概念類似 string evaluation。
  • Pass-by-value-result: 又稱 copy-in, copy-out,不直接傳入變數的參照,反而是將其複製一份傳入參照,最後再把結果指派回原先的變數。

如果以上四種就可以解釋完 Python 的參數傳遞方式,那或許也不需要寫這篇文章了(笑)。嚴格來說,Python 並不屬於以上四種傳遞方式,但是觀念上都是互通的,在這之前,我們必須先了解何謂 Immutable/Mutable。

Immutable Object and Mutable Object

Immutable 代表物件產生後就不可以被修改,反之 Mutable 則是可以被修改。舉例來說 Python 中

numbers, booleans, strings, tuples, frozensets
都是屬於 Mutable Object。

你可能會對 strings 比較好奇:「我常常像這種方式寫啊! string 怎麼會是 Immutable 呢?」

1 2 3 4
s ="Hello" s +=" World!" print s #Print Result: Hello World!
這是因為在第二行的執行時,其實已經產生了另外一個物件 " World!",然後又以兩個字串相接的結果產生了第三個字串物件 “Hello World!",這些隱藏的動作是讓大家容易混淆的地方。Immutable Object 雖然運算上很浪費,但是卻是保證多執行緒下資料一致性(Thread-Safe)的好方法。

Pass Immutable Object by Assignment

引用自官方文件中的 FAQ

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. You can achieve the desired effect in a number of ways.

其實重點就在於一個字:Assignment。每個 Assignment 就會產生對於物件的一個參照。假設以下面的程式為例:

1 2 3 4 5 6 7 8 9 10 11
def foo(a):     printid(a)# 取得 a 所參考物件的記憶體位址     a +=1 a =0 printid(a) foo(a) print a #Print Result: #140463882402880 #140463882402856 #0
我們就可以發現實際上 *foo(a)* 傳入的物件位址已經不同,所以 *foo* 中修改的內容都不會反應在外部的變數 *a* 上,這兩個變數名稱雖然相同,但實際上卻是各別處於不同的記憶體位置中。

Pass Mutable Object by Assignment

當傳入的參數指向的為 Mutable Object 時,狀況就稍微不同了:

1 2 3 4 5 6 7 8 9 10 11 12
def foo(a):     printid(a)     a.append(1)

a =[]
printid(a)
foo(a)
print a
#Print Result
#4461887928
#4461887928
#[1]

我們會發現雖然 a 是兩個不同的變數,但卻都指向相同的 list 物件位址,如同上述所說的,因為 Mutable Object 是允許被修改的,所以 Python 並不會因此複製一個 Object 再將參考傳入,也因此 foo() 中所有的修改都會被保留。也因為 a 各別代表兩個不同的 assignment,如果在 foo() 中重新指派 a 對應的物件的話,修改是不會生效的。

1 2 3 4 5 6 7 8 9 10 11 12 13
def foo(a):     printid(a)     a.append(1)     a =[1,2]

a =[]
printid(a)
foo(a)
print a
#Print Result
#4461887928
#4461887928
#[1]

總結

針對這個議題 Stackoverflow 說明得非常詳細,推薦大家一讀:

The parameter passed in is actually a reference to an object, but the reference is passed by value.

簡單來說,Python 的參數傳遞方式你必須注意:

  • Immutable Object 參數傳遞行為同 pass-by-value。
  • Mutable Object 參數傳遞行為同 pass-by-reference,但是不允許 re-assignment。