在探索到 Python 函數的參數傳遞的時候,我不由讚歎 Python 靈活的參數設計,但慢慢的,開始迷惑與傳遞參數的修改和返回。html
衆所周知,在 C++ 中傳遞參數分爲傳值和傳引用兩種,但 Python 沒有,那到底傳進去的東西,修改一下,能不能傳出來呢?這是一個很奇怪和讓人費解的問題,不是麼?在查閱了一些資料後,對 Python 關於變量、類型和引用的一些基本方式有了一些瞭解,進而基於這種理解並結合實驗,瞭解了參數傳遞的奧妙。
Python 的變量是沒有類型的,這與以往看到的大部分語言都不同。但 Python 倒是區分類型的,那類型在哪裏呢?事實是,類型是跟着內存中的對象走的。Python 的全部變量其實都是指向內存中的對象的一個指針,全部的變量都是!此外,對象還分兩類:一類是可修改的,一類是不可修改的。python
如今,我插入在此先說說函數參數的問題,咱們有下面一個實驗:app
1 2 3 4 5 6 7 8 |
def func1(a): a += 1 def func2(a): a[0] = 0 t1 = 0 func1(t1) print t1 t2 = [1, 2, 3] func2(t2) print t2 |
結果是:函數
1 2 |
0 [0, 2, 3] |
看看結果會不會很驚異?第一個看起來像傳值,第二個看起來卻像傳引用?看到這裏你是否是以爲 Python 是一種莫名其妙的語言?其實當時我也有這種想法……但 Python 果真沒喲讓我失望,它如同 UNIX 同樣,一開始設計得就如此優美。繼續往下看~學習
不可修改的對象是咱們最經常使用和最熟悉,幾乎在任何一個語言中都能看到的——整數、實數、字符串和元組。有人說,怎麼不可變啊?我隨便給他們賦值!是 的,在 Python 裏幾乎一切都是能夠改變的,甚至有人說「若是你願意,None 的值也是能夠變的」(固然我不知道怎麼變……)。可是若是注意觀察,會發現所謂的改變實際上是——扔了舊的建個新的!驗證這個的實驗很簡單:spa
1 2 3 4 |
a = 1 print id(a) a += 1 print id(a) |
相似的實驗想怎麼作怎麼作,只要那兩個是不可變對象,你就必定會發現 id 變了!爲何?由於對象不可變。那什麼可變?變量的引用是可變的!.net
好,那麼天然剩下的就是可變的對象了,上面的實驗亦能夠很容易的證明字典、列表、集合和類實例等對象是可變的。那麼,這意味着什麼呢?設計
下面,咱們回到函數傳值的問題。咱們知道了可變對象和不可變對象的區別,不是嗎?對於可變對象,對於對象的操做不會重建對象,而對於不可變對象,每一次操做就重建新的對象。那麼函數參數究竟是個什麼東西呢?其實說白了也簡單,就是把參數裏傳入的東西對相應對象的引用依次賦給對應的內部變量(有 點暈嗎?)。看看第一個實驗,有沒有明白些什麼?其實都是將一個指向對象的引用傳個一個名爲「參數」的本地變量,因此 func1 中給 a 的是一個值爲 0 的整數對象的引用,但咱們知道,整數對象是不可變的,因此當 func1 對 a 進行修改的時候,其實是修改本地變量 a 的引用到一個新的值爲 1 的整數對象的引用。那麼很顯然,func2 修改的是一個可變的對象,也就是說即便 func2 修改了 a,本地變量 a 和全局變量 t2 指向的仍是同一個對象,雖然他們不是同一個變量!這樣一切狀況都明瞭了,不是麼?不明瞭的話再看看下面這個實驗:指針
1 2 3 4 |
a = [[1, 2, 3], [4, 5, 6]] b = a[0] b[0] = 0 print a |
輸出必定是:code
1 |
[[0, 2, 3], [4, 5, 6]] |
其實原理和參數的傳遞是一致的。
咱們下面來看看全局變量和本地變量的問題。若是一個函數裏面使用了一個變量,那麼 Python 會先看看有沒有對應的本地變量,若是沒有找到,但找到一個全局變量,那麼 Python 會把那個全局變量的引用賦給一個新的本地變量。 因此,如今在函數裏的那個變量和全局變量其實不是同一個變量,他們只不過暫時有了相同的引用。這樣其實能夠看做 Python 爲你作了隱式的參數傳遞。所以咱們發現,他和參數同樣,傳值傳引用表面上看過去漂移不定。那麼如何修改一個指向不可變全局變量的值呢?靠返回值顯然不那麼 優美。好在 Python 像 PHP 那樣提供了一個叫 global 的語法,被 global 的變量使得本地變量成爲相應全局變量的一個別名,也就是說這個語句使他們成爲同一個變量,這一點很重要!
如今看到了 Python 優美的設計。那下面的問題是,若是咱們必定要複製一個可變對象的副本怎麼辦?簡單的等號賦值顯然被證實無效了。Python 也提供了方法——copy 模塊。copy 模塊是每個 Python 都有的,專門用於生成可變對象的副本。copy 模塊中有兩個函數:copy.copy 和 copy.deepcopy。其中 copy 叫作潛複製,它僅僅複製了第一你給它的東西,下面的無論了。而 deepcopy 叫作深複製,它將全部能複製的都複製了。這樣說比較抽象,咱們來看下面實驗:
1 2 3 4 5 6 7 8 9 10 |
a = [[1, 2, 3], [4, 5, 6]] b = a c = copy.copy(a) d = copy.deepcopy(a) a.append(15) a[1][2] = 10 print aprint bprint cprint d |
輸出結果:
1 2 3 4 |
[[1, 2, 3], [4, 5, 10], 15] [[1, 2, 3], [4, 5, 10], 15] [[1, 2, 3], [4, 5, 10]] [[1, 2, 3], [4, 5, 6]] |
我想,效果不言而喻了。
此外,我還看到一個叫作弱引用 (weakref) 的模塊,暫時不知道是幹嗎的……下次研究了再說……
參考: