Python學習之引數(一)

ARM的程式設計師敲著詩歌的夢發表於2019-03-21

Python學習之引數(一)

引數的傳遞

所有的引數實際上都是通過指標進行傳遞的。作為引數被傳遞的物件從來不自動拷貝。

在函式內部的引數名的賦值不會影響呼叫者。

如果傳入的是可變物件,那麼在函式內就可以就地改變這個可變物件,這可能會影響呼叫者。

Python通過賦值進行傳遞的機制與C++的引用引數選項並不完全相同,實際上它與C語言的引數傳遞模型相當相似。

不可變引數“通過值”進行傳遞。像整數和字串這樣的物件是通過物件引用而不是拷貝進行傳遞的,但是因為你無論怎樣都不可能在原處改變不可變物件,所以實際效果就像建立了一份拷貝。

可變物件是通過指標進行傳遞的。例如,像列表和字典這樣的物件也是通過物件引用進行傳遞的,這一點與C語言使用指標傳遞陣列很相似:可變物件能夠在函式內部進行原處改變,這一點和C陣列很像。

以上只是偏重於理論的敘述,如果想搞清楚Python的引數傳遞機制,恐怕要研究幾個例子。

考慮如下程式碼:

>>> def f(a):                 # a is assigned to (references) the passed object
        a = 99                # Changes local variable a only
>>> b = 88
>>> f(b)                      # a and b both reference same 88 initially
>>> print(b)                  # b is not changed
88

第3行,b引用了88,可以認為b指向了88;

第4行,呼叫函式f,把b傳了進去,實際上傳遞的是一個指標;

第1行,這裡的a擁有了b的值,實際上是a也指向88;

第2行,a又指向了99;

第5行,列印b,此時b已然指向88,所以結果是88。

我們們再看一個例子。

>>> def changer(a, b):        # Arguments assigned references to objects
        a = 2                 # Changes local name's value only
        b[0] = 'spam'         # Changes shared object in place
>>> X = 1
>>> L = [1, 2]                # Caller:
>>> changer(X, L)             # Pass immutable and mutable objects
>>> X, L                      # X is unchanged, L is different!
(1, ['spam', 2])

從第8行的結果來看,X 並沒有改變,但是 L 從 [1, 2] 改變為 [‘spam’, 2]

X 沒有改變,這和第一個例子是同樣的道理。

我們分析一下 L 為什麼會改變。第1行的b也是一個本地變數,但是一個可變物件傳給了它——b指向了一個列表,它和L共享同一個物件。第3行,對b[0]進行賦值的結果會在函式返回後影響L的值。

下圖中說明了函式呼叫前和呼叫中變數名與物件的繫結關係。

在這裡插入圖片描述

在呼叫changer函式之前,X 指向 1,L 指向列表 [1, 2] ;在函式呼叫的時候,X的值(物件1的地址)被傳遞給 a,所以 a 也指向1,同理,b 指向列表 [1, 2] ;因為 1 是不可變物件,所以 a = 2 並不會修改物件 1,而是使 a 指向 2,但是列表是可變物件, b[0] = ‘spam’ 確實修改了列表 [1, 2] ,使其變成了 [‘spam’, 2]。

如果上面的內容依然令你困惑的話,你可以這麼理解,通過函式呼叫進行引數賦值與執行下面一系列簡單的賦值語句是等效的:

>>> X = 1
>>> a = X               # They share the same object
>>> a = 2               # Resets 'a' only, 'X' is still 1
>>> print(X)
1
>>> L = [1, 2]
>>> b = L               # They share the same object
>>> b[0] = 'spam'       # In-place change: 'L' sees the change too
>>> print(L)
['spam', 2]

避免可變引數的修改

對可變引數的原處修改的行為不是一個 bug——它只是引數傳遞在Python中的工作方式。在Python中,預設通過引用(也就是指標)進行引數傳遞,因為這通常是我們想要的。這意味著不需要建立多個拷貝就可以傳遞很大的物件,並且能夠按照需要方便地更新這些物件。

如果不想要函式內部在原處的修改影響到傳遞給他的物件,那麼可以建立一個原物件的拷貝。例如:

L = [1, 2]
changer(X, L[:])        #通過切片傳遞拷貝,所以L不會改變

如果不想改變傳入的物件,無論函式是如何呼叫的,我們同樣可以在函式內部進行拷貝。

def changer(a, b):
    b = b[:]            # Copy input list so we don't impact caller
    a = 2
    b[0] = 'spam'       # Changes our list copy only

—未完待續—

參考資料

《Python學習手冊(第4版)》,機械工業出版社

《Learning Python 5th Edition》, O’Reilly

相關文章