從 JavaScript 角度學 Python(27) - 傳值?傳參考?
前言
已經快到鐵人賽的結尾了,但是我現在才想到我好像少講了一個東西,就是關於傳值與傳參考的部分,所以這一篇就來聊一下關於傳值與傳參考吧。
傳值
首先先簡單聊一下純粹的傳值,在 JavaScript 中我們知道原始型別是單純傳遞一個值給另一個變數,因此修改時並不會影響原有的變數:
1 | var a = 'Ray'; |
那話題拉回到 Python 中,Python 與 JavaScript 也是相同的嗎?我們可以直接來看一次範例:
1 | a = 'Ray' |
從結果來看,我們可以看到 Python 與 JavaScript 的結果是一樣的。
但是如果今天採用的是另一種方式來呈現的話結果會相同嗎?這邊就讓我們來看看另一種範例程式碼:
1 | def fn(x, y): |
我們可以看到在上面的範例程式碼中,我將全域的變數 x
與 y
傳入到 fn
函式內,接下來在 fn
中宣告一個 cache = 100
的變數,而後面我做了重新賦予 x
與 y
的行為,但是你會發現 fn
內的調整並不會影響全域變數的結果,這代表著什麼呢?代表著我們只是傳遞值 (x
, y
) 給 fn
函式的參數使用,因此函式內部的變化它並不會影響原本的變數。
或許這個時候你會很聰明的想到我在「從 JavaScript 角度學 Python(6) - 變數作用域」章節中有介紹一個「global
」的方式可以針對外部變數做一些ㄌ調整,但是基本上如果你在這邊使用 global
方法的話是會出現錯誤的:
1 | def fn(x, y): |
而會出現「SyntaxError: name 'x, y' is parameter and global
」這一段錯誤訊息的主要原因在於,函式的參數如果與 global
指定的變數相同的話,是會無法正常運作的,因為它會不知道你到底是要使用哪一個參數還是變數唷~
所以我們這邊可以大膽的推測…
「或許 Python 的數值型別、字串型別與布林型別會與 JavaScript 的原始型別類似?」
那麼關於這個推測我們可以試著實踐一次或許會更精準,透過前面幾個章節我們了解到容器型別類似於 JavaScript 的陣列與物件,所以這邊我們可以試著撰寫字串、布林與數值型別來測試看看:
1 | def fn(w, x, y, z): |
透過上面的結果來看,基本上 Python 的字串、布林與數值型別確實是與 JavaScript 的基本型別雷同,那字典與串列呢?別急,我們先接著下去看,但是這邊要注意我們是在講 Python 不是 JavaScript:
傳參考
接下來聊一下剛剛沒有提到的字典與串列的部分,雖然我們知道 Python 的字典與串列就是對應著 JavaScript 的物件與陣列,但實際上運作模式會是一樣嗎?所以這邊也來快速聊一下這一塊。
首先我們先來一段 JavaScript 很常見的物件傳參考的基本範例程式碼:
1 | var b = { |
上面的觀念我們都知道這是一個物件傳參考概念,如果對於這一段有興趣的話可以閱讀我這一篇文章「JavaScript 核心觀念(27)-物件-物件參考觀念的實際運作模式」會有更詳細的說明,因此這邊就不著墨了(笑)。
前面講那麼多,那 Python 的狀況又如何呢?我們將上面的 JavaScript 範例程式碼直接改寫成 Python 版本試試看:
1 | b = { |
好吧…依照輸出的結果來看, Python 的字典跟 JavaScript 的物件傳參考狀況非常像沒有錯吧~
接下來讓我們看看另一個範例程式碼:
1 | def fn (x): |
上面這個範例程式碼確實沒有任何問題,感覺也是物件傳參考有非常大的關聯性,但是如果範例程式碼變成了以下,那麼結果會是怎樣呢?
1 | def fn (x): |
疑?奇怪了,為什麼結果不一樣了?
好吧,這邊要講一下關於物件(字典)傳參考的時候會有一個很特別的地方,雖然我們知道 Python 的字典基本上與 JavaScript 非常雷同,因此再將字典賦予到變數時,其實是賦予一個記憶體空間位置,我知道這邊有點難懂,所以我想換個方式形容,我們試著把容器型別與數值型別、字串型別以生活化的角度去理解看看。
而這邊會用車子去當作舉例,因此數值型別與字串型別你可以把它想像成是車子內的某些東西,例如:輪胎、方向盤,又或者收音機等等,字典與串列的話則是一個僅有車殼的車子(連引擎都沒有),所以透過這樣的舉例來講,可能就會像這樣:
1 | # 把字典看成一個車殼,裡面放著車子的東西 |
那麼當我將 car
字典賦予到另一個變數時,其實概念類似我把車子借給他人使用,例如我借來了一台車:
1 | car = { |
所以當我對著車子有任何調整,可能拆掉一個輪胎,那就會影響到原本的車子,畢竟車子是借來的:
1 | car = { |
那出借東西的行為就是 JavaScript 的物件傳參考,我只是借給你用而已的概念。
那麼有一種狀況下我們不會影響到原有的車子,也就是我後來終於有錢買自己的車子:
1 | car = { |
因為後來自己買了一台車子的關係,所以我就不再跟人家借車子,所以我接下來不管怎麼調整自己的車子都不會影響到原本 car
變數:
1 | car = { |
上面的舉例只是希望讀者在閱讀時可以比較好理解,因此只要你看到變數被重新賦予一個字典 or 物件,就代表他會重新指向到一個新的記憶體空間(空車殼),如果這樣子還不好理解的話,以後你只要看到變數被重新賦予一個 {}
or []
就代表著它被重新指定了位址,因此就不會發生所謂的物件傳參考問題。
當然你也可以活用前面章節所學的 id
來查看彼此的記憶體空間是否相同:
1 | car = { |
如果是重新賦予的話,則是不同的記億體位置:
1 | car = { |
透過前面章節所學的某些技巧也可以幫助到你理解,因此前面章節所分享的觀念都是非常重要的唷~
call by xxx
最後其實你應該會發現我一直刻意閃過一些東西不講,例如…call by xxx or pass by xxx 什麼的,最主要原因是我自己對於 Python 沒有非常的熟悉,所以自己也不敢多講什麼,主要也是怕講錯。
但是如果真的硬要問我的話,我可能會依據 wiki 所說的來講,因為以 wiki 的描述來講,Python 是比較接近 Call by sharing
傳共享物件呼叫(Call by sharing)的方式由Barbara Liskov命名[1],並被Python、Java(物件類型)、JavaScript、Scheme、OCaml等語言使用。
而這一點也剛好跟 JavaScript 非常像,因此我自己覺得你想把 Python 看成 JavaScript 似乎也可以?!
最後的最後也想聊個好玩的東西,也就是以下程式碼:
1 | a = 'Ray' |
上面你可以發現我並沒有 a = b
,但是兩者的記憶體位置卻是相同,因此代表著字串在建立時都是指向同一個記憶體位置,也因此才會導致 a == b
是一個 True
,而這一段我覺得可以理解下面這張圖:
但是如果重新賦予其他值的時候,就會是直接指向到另一個記憶體位置:
如果是字典的話,那麼也是類似,但是字典則是看到一個新的 {}
or []
才會重新指向,這邊就讓我偷懶一下用之前 「JavaScript 核心觀念(27)-物件-物件參考觀念的實際運作模式」的文章圖片偷懶一下:
那今天也差不多介紹到這邊就告一個段落囉~
參考文獻
作者的話
中秋節那段時間因為回老家團圓的關係,所以訊號一直都很差,幾乎只能打電話不能上網的等級,所以中秋節的晚上我都只能站在馬路正中央收發訊息,收發完就要快點閃離馬路,不然我現在應該不會在這邊寫文章了…
關於兔兔們
- Tailwind CSS 臺灣官網
- Tailwind CSS 臺灣 (臉書粉絲專頁)
- 兔兔教 × Tailwind CSS Taiwan (臉書社群)
- 兔兔教大本營