在不同程式語言中使用到函式, 在傳遞參數時總是有分是傳值或是傳址。前者, 傳值就是在函式外變數的值, 不受函式內對參數值的變更而影響, 除非是在函式內部做了賦予外部變數新值的動作, 因此傳值可以看成是在函式內另生成一個新位址來存放傳入的值。相對地, 傳址就是把函式外變數的位址, 傳遞給函式內部使用, 可以被看成是同一個物件來操作, 因此, 函式內部對參數做的異動, 會影響到函式外部的變數值。
而在部份程式語言中, 對函式參數的傳值與傳址, 有些是以傳值為主的, 有些則是以傳址為主的, 使用時要特別注意, 若是要明確指定, 則是在傳入時可以加上關鍵字來標示, 例如C#中是用ref, 在T-SQL預存程序中是用output。另外, 對於不同型別的物件也有不同的定義特性, 例如, 對基底型別是以傳值為主, 如string、int、bool 這類等等; 而對其他型別或複雜型別則是以傳址來處理。如果要對基底型別改成傳址處理, 就要加上關鍵字, 才能轉成傳址處理。
這次在使用Python時體驗到與以往不同的使用方式, 和其他語言不同的是, Python在傳入時是沒有關鍵字可以指定是傳值或是傳址的。而預設情況下, 基底型別是以傳值處理, 如str、int、bool 這類等等, 其他的則是傳址, 如list。先來宣告幾個變數:
s="hello"
b=False
n=1
l=['str outside']
s2=s
b2=b
n2=n
l2=l
簡單的宣告4個字串、布林值、數值、List類型(s,b,n,l), 同時我也宣告一組變數(s2,b2,n2,l2), 由前一組直接指定, 這時直接來比對2組是否是同一個位址:
print(s==s2)
print(b==b2)
print(n==n2)
print(l==l2)
print(s2)
print(b2)
print(n2)
print(l2)
前4行==比對的結果是都回傳True, 而後4行print出來的值也都與原來變數相同, 但這是代表2組變數對應到同樣位址嗎? 再來指定給第2組新值, 並print出原來第1組的值來看看:
s2="str 2"
b2=True
n2=3
l2.append("set l2 val")
print(s)
print(b)
print(n)
print(l)
結果是:
hello
False
1
['str outside', 'set l2 val']
看來針對前3個字串、布林值、數值變數屬於基底類型型別, 是用傳值; 而List則是用傳址。再來宣告一個函式:
def myfun(_str,_bool,_num,_list):
_str="world"
_bool=True
_num=2
_list.append('set in def')
然後呼叫此函式, 並把相關變數再print出來看一下:
myfun(s,b,n,l)
print(s)
print(b)
print(n)
print(l)
結果是:
hello
False
1
['str outside', 'set l2 val', 'set in def']
可以看到, 針對基底類型型別, 是用傳值的方式處理, 所以在呼叫函式後再print出來的, 仍是在外面宣告變數時指定的值, 沒有被函式內的異動改變; 而對List則是用傳址的方式處理, 所以print出來的已是被函式內變動過的結果。
若是要針對基底型別改成有如傳址處理, 則要在函式的return上做標示。來看另一個函式宣告:
def myfunReturn(_str,_bool,_num,_list):
_str="return str"
_bool=True
_num=2.0
_list.append('set in def return')
return _str,_bool,_num,_list
在最後return時可以指定給多項要回傳的變數, 而在呼叫時則要如下寫法, 依序拿變數來承接:
s2,b2,n2,l2 = myfunReturn(s,b,n,l)
print(s)
print(b)
print(n)
print(l)
print(s2)
print(b2)
print(n2)
print(l2)
結果是:
hello
False
1
['str outside', 'set l2 val', 'set in def', 'set in def return']
return str
True
2.0
['str outside', 'set l2 val', 'set in def', 'set in def return']
看到函式在return上傳回各個參數, 呼叫的第49行也是用外部的變數依序去承接, 這樣就能把函式內異動的結果重新指定回外部了, 如第2組變數的值已變成函式中重新指定的, 而原來第1組變數仍未被改變, 但唯獨List仍是傳址處理。
所以正確應該看成字串、布林值、數值這些基底型別仍是用了傳值, 只是這裡return的寫法可以把函式內異動過的值, 用簡單的寫法再次指定回函式外部, 達成一個類似傳址的效果而已, 所以如果要更接近其他語言的用法, 或許是承接變數也改用原來第1組的, 如下:
s,b,n,l = myfunReturn(s,b,n,l)
print(s)
print(b)
print(n)
print(l)
結果是:
return str
True
2.0
['str outside', 'set l2 val', 'set in def', 'set in def return', 'set in def return']
看到s,n,b變數已重新指定成回傳值了。