6.4. 參照及傳入參照


接下來要介紹經常使用但是不容易注意到的兩個概念:

  • 參照 (reference)
  • 傳入參照 (pass-by-reference)

先前介紹的是參照,當使用「=」指派運算子指派一個值給一個變數,同時也表示這個變數指向儲存該值的記憶體位址。在下列的範例中,變數 x 及 y 皆指向整數1,但是當變數 y 被指派新的值之後,變數 y 就指向整數2的記憶體位址了。

>>> x = 1
>>> y = x

>>> id(X)
1952368304

>>> id(y)
1952368304

>>> y = 2
>>> y
2

>>> id(y)
1952368336

然而,若變數 x 及 y 皆指向一個串列(或字典),隨後修改了串列內容,此時可以發現變數x 及 y 所指的串列內容都改變了,這是因為使用串列時是指派串列的「參考」給變數 x 及 y,而這個參考又指向(或儲取、收集)串列中各個資料的記憶體位址。

>>> x = [1 , 2, 3]
>>> y = x
>>> id(x)
31459080

>>> id(y)
31459080

>>> for i in range(0, len(x)):
...     print(i, x[i], id(x[i]))
...
0 1 1952368304
1 2 1952368336
2 3 1952368368

>>> for i in range(0, len(y)):
...     print(i, y[i], id(y[i]))
...
0 1 1952368304
1 2 1952368336
2 3 1952368368

>>> y[0] = 4
>>> y
[4, 2, 3]

>>> id(y)        # 變數指向的記憶體位址不變
31459080

>>> for i in range(0, len(y)):
...     print(i, y[i], id(y[i]))
...
0 4 1952368400    # 但是,串列中資料的記憶體位址改變了
1 2 1952368336
2 3 1952368368

>>> x
[4, 2, 3]

>>> idx(x)
31459080

>>> for i in range(0, len(x)):
...     print(i, x[i], id(x[i]))
...
0 4 1952368400
1 2 1952368336
2 3 1952368368

所以,可以從上述幾個範例中得知,當一個函數的參數是一個串列則是傳入串列的參考,即稱為「傳入參考」。在程式設計的實作上,當函數的參數是串列(或字典)且會對串列做修改時,需要特別注意這種情況是會導致bug的產生。

>>> def change_list(papameter):
...     papameter[0] = '4'
...

>>> x = [1, 2, 3]
>>> id(x)
32223048

>>> change_list(x)
>>> x
['4', 2, 3]

>>> id(x)
32223048

使用 copy 模組來複製資料

如果函數的參數需要是串列及會修改此串列時,可以使用 copy 模組的 copy() 來複製串列。

>>> def change_list(papameter):
...     papameter[0] = '4'
...

>>> x = [1, 2, 3]
>>> id(x)
32223176

>>> import copy
>>> y = copy.copy(x)
>>> id(y)
42420296

>>> change_list(y)
>>> y
['4', 2, 3]

>>> x
[1, 2, 3]

若串列中有包含其他串列時,則使用 deepcopy()。但是,如果有大量使用deepcopy()時,其程式的效能會比使用copy()來得差。

>>> def change_list(parameter):
...     parameter[0].append('6')
...

>>> x = [[1, 2, 3,], 4, 5]
>>> id(x)
31459080

>>> import copy
>>> y = copy.copy(x)
>>> id(y)
42421768

>>> change_list(y)
>>> y
[[1, 2, 3, '6'], 4, 5]

>>> x
[[1, 2, 3, '6'], 4, 5]

>>> y.append(7)
>>> y
[[1, 2, 3, '6'], 4, 5, 7]

>>> for i in range(0,len(y)):
...     print(i, y[i], id(y[i]))
...
0 [1, 2, 3, '6'] 42420296
1 4 1952368400
2 5 1952368432
3 7 1952368496

>>> x
[[1, 2, 3, '6'], 4, 5]

>>> for i in range(0,len(x)):
...     print(i, x[i], id(x[i]))
...
0 [1, 2, 3, '6'] 42420296
1 4 1952368400
2 5 1952368432
>>> def change_list(parameter):
...     parameter[0].append('6')
...

>>> x = [[1, 2, 3,], 4, 5]
>>> id(x)
32223176

>>> import copy
>>> y = copy.deepcopy(x)
>>> id(y)
31459080

>>> change_list(y)
>>> y
[[1, 2, 3, '6'], 4, 5]

>>> for i in range(0,len(y)):
...     print(i, y[i], id(y[i]))
...
0 [1, 2, 3, '6'] 42421832    # 使用deepcopy()時,串列中的串列的記憶體位址會不同
1 4 1952368400
2 5 1952368432

>>> x
[[1, 2, 3,], 4, 5]

>>> for i in range(0,len(x)):
...     print(i, x[i], id(x[i]))
...
0 [1, 2, 3] 42420360        # 使用deepcopy()時,串列中的串列的記憶體位址會不同
1 4 1952368400
2 5 1952368432

參考資料

  • Python自動化的樂趣, 第四章, Al Sweigart 著、H&C 譯, 碁峰
  • Python編程入門第3版(簡), 第五章, Toby Donaldson著, 人民郵電出版社

results matching ""

    No results matching ""