案情不單純 Python (1)— How objects are passed to Functions
TL;DR (Too Long; Didn’t Read): Neither pass by value, nor pass by reference — Pass by sharing.
前言
我總是在無聊的時候胡思亂想,也正是這種時候最會幫自己挖坑。
半年前我在當兵時,一些之前沒有認真思考的問題又浮現在我腦中,所以偶爾就會趁著週末出關研究一下。收假過後看著一成不變的光頭配迷彩衣,我心想:「不行,等我出來一定要搞些不一樣的!」所以現在才會有這篇文章的誕生。我想把一些我認為有趣的問題用文字寫下來,在激發更多討論之餘也順便紀錄自己geek的一面。
很多人認為Python是最簡單的程式語言之一,它簡潔易用的特性對使用者來說真的是一大福音。但是這也常間接讓我們止步於對Python表層的理解、忽略了許多細節,然而這些細節有時卻是成為一個更好的程式設計師所必備的。
因此我希望透過「案情不單純Python」這一系列文章來探討一些較為深入、有趣的Python知識。
總之坑都挖了,so let’s dive right in!
Introduction
如題,本篇文章會就「Python如何傳遞參數到函式中」做一些探討。
函式(Function)是程式語言中非常常見的工具,能讓程式避免不必要的重複部分,也將不同的功能分開讓程式更易懂也更好debug。通常函式會接收一至多個argument (參數),那當你在調用這個函式時,「這些參數到底是用什麼方法傳入函式的呢」?
不同的程式語言對傳遞參數到函式中有不同的策略,常見的策略像C++就有pass by value 和 pass by reference 這兩種。
- Pass by value: 當一個參數是 pass by value,對於參數而言,呼叫與被呼叫者是獨立的變數,程式會複製一份與呼叫者相同的值給被呼叫者。因此若在函式內部改動被呼叫者,在外界的呼叫者並「不會」受到影響。
- Pass by reference: 當一個參數是 pass by reference,呼叫與被呼叫者,都使用相同的變數。因此若在函式內部改動被呼叫者,在外界的呼叫者「會」受到相同的影響。
下面是簡單的C++例子,可以看到變數a
跟b
分別使用pass by value跟pass by reference。在函式內部都將變數從1改成6,結果跳出函式後只有pass by reference的變數b
被影響了:
void pass_by_value(int a){
a = 6;
return;
}void pass_by_reference(int& b){
b = 6;
return;
}int main() {
int a = 1;
int b = 1; pass_by_value(a);
pass_by_reference(b); cout << a << endl; // *** output: 1 (stay unchange) ***
cout << b << endl; // *** output: 6 (changed!) *** return 0;
}
但是Python沒有C++的指標(pointer, *
),也不能在定義函式時指定輸入參數是一個reference &
,所以Python到底是pass by value還是pass by reference呢?亦或是Python黑魔法會幫我們自動判斷這個參數要pass by value還是pass by reference?
如果你看完上一段末的問題心中也有點疑問,或是你選擇了「pass by value」、「pass by reference」、「Python黑魔法會幫我判斷」其中一項,那也建議你跟我一起往下看!
理解 Variable Assignment
首先,我們先來釐清一下這行簡單的Python程式碼:
n = 10
這…這有什麼好釐清的,「n等於10?」、「n是10?」@@
要正確理解這行程式碼,我們需要知道變數(Variable)跟它所涉指到的物件(Object)是不一樣的。以這個例子來說,n
就是變數,而10
就是變數 n
所涉指到的整數物件(Integer Object)。所以這行程式碼可以被翻譯成「變數n被指派了(assigned)10
這個整數物件」或「10
這個整數物件被指派給了變數n
」,簡單點來形容的話,n就像是一個標籤貼在10
這個整數物件上。
這時,你突然心情不好,在這行程式碼下面重新將另一個String Object指派給了變數n
,
n = ":("
此時,你將n
這個標籤從10
這個物件撕下來重新貼到另一個物件":("
上。而原本的10
這個物件就會因為已經沒有任何其他變數涉指到它,所以已經無法被我們取得,此時Python就會自動將這個無法被取得的物件的Memory釋放,供其他地方使用。
Chained Assignment
Python中還可以允許Chained Assignment如下,
# method 1.
a = b = c = 300
就像是把三個變數a, b, c也就是三個標籤都貼在「同個物件」(這邊是整數物件300
)上。
呃…這又有什麼好大驚小怪的?
表面上這樣只是Python中簡潔的語法,能夠讓我們不用寫那麼多Code:
# method 2.
a = 300
b = 300
c = 300
就將a,b,c的值設定為300。但是method 1.的寫法其實跟method 2. 有根本上的差異:method 2. 會產生三個不同的整數物件300
並將變數a,b,c「分別」貼到三個「不同的物件」上。我們會在下面介紹 id()這個內建function的時候再用它實際做一些小實驗~
用id()來了解變數被指派哪個物件
一個Python物件在被建立之後(別忘了所有東西在Python中都是物件!),每個物件都會有其獨一無二的Object Identity,並且任意兩個物件的Object Identity在它們的lifetimes(建立到消失)中不會相同。
不那麼文鄒鄒的話,可以簡單理解成每個被創造的物件,在這個物件消失前都會持有一個身分證(Identity)。好比進高中時每個人(Object)都會有自己的學號(Identity),在你畢業之前不會有其他在學生跟你的學號重複,並且當你高三畢業後你的學號又被釋放出來給新進的高一同學(another Object)使用。(lifetime:整個高中時期)
“id()” 是一個Python的內建函數(built-in function),是用來查詢「一個變數所涉指的物件的Identity」。舉一個簡單的例子:
>>> a = 10>>> id(a)
4305283744 # same!
>>> id(10)
4305283744 # same!>>> a is 10
True
如果我們將「變數a
作為id()的argument」,會得到a
所涉指的物件的identity(即整數物件10
的Identity),所以可以看到id(a)和id(10)的identity是一樣的!
is
這個Python關鍵字會比較左右兩邊「是不是同一個物件」。**要注意is
和較常見的==
operator不一樣的地方在於:==
只比較左右兩邊的「值」是否相等,不一定要是同一個物件。(延伸閱讀: “is” and “==” operators in Python)
雖然並不影響我們理解 id()這個 function,但這邊其實有一個小問題,不知道各位有沒有發現 XD 我會在下面的 “Caching small integers values” section做進一步的說明,這邊我們只要先理解 id()的用途就可以了~
再來我們回到上一個section的例子,
# method 1.
a = b = c = 300# method 2.
a = 300
b = 300
c = 300
當我們用 id()來查看變數所涉指的物件時,
# method 1.
>>> a = b = c = 300>>> id(a)
4331029424 # same!
>>> id(b)
4331029424 # same!
>>> id(c)
4331029424 # same!>>> a is b is c
True
可以看到三個變數a,b,c其實都是指到「同一個物件」,跟下面的程式碼作用一樣:
>>> a = 300
>>> b = a
>>> c = a # or `c = b`>>> a is b is c
True
那如果換成method 2.呢?
# method 2.
>>> a = 300
>>> b = 300
>>> c = 300>>> id(a)
4331029424
>>> id(b)
4331029328 # changed!
>>> id(c)
4331029520 # changed!>>> a is b is c
False
我們會發現這三個變數a,b,c雖然數值都是300,卻是指到「完全不同的三個物件」,和我們上個Section的說明一致。
指到不同的 物件…所以呢?目前我們所介紹的 variable assignment還看不出這兩者會對程式產生什麼不一樣的後果,但下面介紹 data manipulation時就會看到兩者的差異了~
(Optional) 深入理解:Caching Small Integer Values
這個段落不看也不影響本篇所介紹的概念!
經過前面的介紹,現在下面這段程式碼應該大家都可以輕易理解,
>>> a = 300
>>> b = 300>>> id(a)
4331029584 # different!
>>> id(b)
4331029424 # different!>>> a is b
False
變數a跟b隨然都是300,但是兩個變數涉指到兩個不同的300
整數物件,這非常合理。
但是,如果我們換個數字,
>>> a = 5
>>> b = 5>>> id(a)
4305283584 # same!
>>> id(b)
4305283584 # same!>>> a is b
True
神奇的是,這兩個變數卻指到同個整數物件5
了…難道是黑魔法?
原因是:為了優化程式,直譯器會在一開始先幫範圍在[-5, 256]之間的整數創建它們的物件,並且在程式執行中重複利用這些物件。所以當你的變數指向這個範圍內的整數時,這些變數會全都指向同一個整數物件!
所以我們回到先前埋下的伏筆:
# 為了深入說明,這邊我在每一行code前面加上行號1~4
(1)>>> a = 10(2)>>> id(a)
4305283744 # same!
(3)>>> id(10)
4305283744 # same!(4)>>> a is 10
True
前面說這一段Code證明了id(a)會回傳a
所指到的物件的identity,那到底有什麼小問題呢?第一行(1)把10
這個Integer Object指派給變數a
並沒有問題,(2)用id(a)
查看a
所涉指物件的identity也沒有問題。但是第三行(3)跟第四行(4)問題來了,此時我們用 id(10)
這行code來查看這個整數物件10
的identity,但是這個10
跟第一行我們所指派給變數a
的10
應該要是不同的物件才對啊?同理,第四行程式也有一樣的問題。會產生這樣結果的原因就是這一個小節所提到的”Caching Small Integer Values”,因為Python會重複利用小的整數物件,所以這邊的第三行跟第四行出現的10才會跟變數a所涉指的10是同一個物件!若將數字換成不在[-5, 256]範圍內的數字,第三跟第四行就不成立囉!
Ex:
>>> a = 300>>> id(a)
4348855184
>>> id(300)
4348855120 # changed!>>> a is 300
False # Not the same!
更有趣的是,當我們把Integer換成String時:
# Example 1
>>> c = 'awefawawefawefawef'
>>> d = 'awefawawefawefawef'
>>> c is d
True# Example 2
>>> a = 'KY! What Have You Learned'
>>> b = 'KY! What Have You Learned'
>>> a is b
False
Example 1的c,d竟然也指到同一個String Object!但是也並非所有的String都會產生一樣的結果,在Example 2中String裡面有空格 ,結果a,b就指向兩個不同的物件了(並非驚嘆號所造成的)!
這邊的理論我也不是很清楚,只是發現這個現象覺得很有趣,有興趣的讀者可以自行測試或是留言分享給我XD
Variable Assignment 與 Data Manipulating
上一章節我們已經談到Variable Assignment,了解一個Python物件(Object)和變數(Variable)之間的關係,並且用內建函數 id()來實際查看一個變數所涉指的物件的identity。
這一章節我們要來討論上面花了一大堆篇幅談過的「Variable Assignment (變數指派)」和「Data Manipulation (資料操作)」的差別。簡單來說,如果Variable Assignment像先前提到的是將標籤(變數)貼在某個Python物件上,那Data Manipulation就像是「修改某個Python物件內部的資料」,並不會動到標籤(變數)。
為了進一步討論這兩者的差別,我們必須對Python中的可變物件(Mutable Object)以及不可變物件(Immutable Object)有一定的認識。
Mutable and Immutable Objects
所有東西在Python中都是物件,而這些物件又被區分為兩大類:可變(Mutable)跟不可變(Immutable)物件。可變物件的內容(content)或狀態(state)可以被更改,而不可變物件則無法。
Mutable objects:
list, set, dict, byte array, etc.
Immutable objects:
int, float, complex, string, tuple, bytes, etc.
我們前面所舉的例子都是整數(int)跟字串(string),兩種都是不可變物件,所以這兩種Python物件的內容都無法被更改!
舉個簡單的例子:
>>> a = 5
>>> id(a)
4305283584>>> a = a + 1 # manipulation??? Nah :P
>>> a
6
>>> id(a)
4305283616 # different!
# a new integer object is `assigned` to variable a.>>> id(6)
4305283616 # (optional) caching small integer values.
原本的變數a
指到一個整數物件5
,後來我們將a
改成a+1
,這個步驟並不是data manipulation,只是將兩個整數物件:5
(變數a
所指)跟1
加起來得到一個新的整數物件6
,並將這個新的整數物件6
重新 「指派 (assign)」給了變數a
,所以這邊的操作對a
來說就只是variable assignment罷了。
如果有看 “Caching Small Integer Values” section的話,這個例子的最後一行code應該可以再次看出來,變數
a
的確是被指派了一個新的整數物件6
。因為Python會cache small integer的關係,程式中所有整數物件6
都會是同一個,id(a)和 id(6)相等也證明了變數a
不是「修改(manipulate)了原本整數物件5
的值」,而是「被指派(assigned)了一個新創造的整數物件6
」~
會產生這種結果的原因背後其實就是不可變物件的特性在作祟!因為你不能更改一個不可變物件的內容。所以不管如何操作不可變物件,它都不可能是data manipulation,因為Python根本就不給你修改(manipulate)這些不可變物件(但後面會提到例外)。那想當然,既然你不可能改變物件內容,那就只能創造新的物件(Python會偷偷幫你做)再將舊物件上的標籤(變數)撕下來重新貼(assignment)到新物件上囉。
上面的例子任意換成其他不可變物件也都成立!
那可變物件(Mutable Object)呢?可變物件很直觀的就是允許我們更改「這個物件」的內容,所以我們不需要創造新的物件出來。舉個簡單的例子,
>>> a = [1,2,3] # list object is a `Mutable Object`.
>>> id(a)
4341615496 >>> a.append(4) # data manipulation
# other data manipulation methods: `+=`, extend()>>> a
[1, 2, 3, 4]
>>> id(a)
4341615496 # same!!!
List是可變物件,我們可以用 .append()
來更改(manipulate)「這一個List物件」的內容。更改完之後再用id()來看變數a
所指到的物件有沒有改變?答案是沒有,這次真的是修改了原本List Object裡面的內容,沒有創造新的List Object出來。
當然可變物件的操作也不一定要是data manipulation,你想要使用variable asssignmennt也是沒有問題的。例如:
>>> a = [1,2,3]
>>> id(a)
4341630856>>> a = a + [4] # NOT data manipulation (assignment)
# However, using `a += [4]` will be data manipulation.>>> a
[1, 2, 3, 4]
>>> id(a)
4341615496 # changed!
基本上就跟不可變物件的操作一樣!讀者也可以自行將上面兩個例子替換成其他「可變物件」試試看~
誒等等…這兩個例子只差在一個操作用
.append()
,另一個用+
而已,那我要怎麼知道哪些方法是 data manipulation,哪些不是呢? 此時就要去爬 Python官方文件、各大論壇或詢問 Google大神,基本上都會找得到每個方法背後的操作細節,一起當個稱職的人體爬蟲機吧!
不可變物件的例外 (Exceptions in immutability)
不過要注意一點:也不是所有不可變物件的內容都無法被改變!我們看一個例子:
def change_list_content_in_tuple(a):
a[1].append(4)if __name__ == '__main__':
a = ("foobar", [1,2,3])
print(f"id(a[0])={id(a[0])}, id(a[1])={id(a[1])}") change_list_content_in_tuple(a)
print(a)
print(f"id(a[0])={id(a[0])}, id(a[1])={id(a[1])}")
""" outputid(a[0])=4348024120, id(a[1])=4350004040 --> same!
('foobar', [1, 2, 3, 4]) --> content changed, but binding hasn't!
id(a[0])=4348024120, id(a[1])=4350004040 --> same!"""
雖然tuple Object本身是不可變物件,但是我們還是可以更改(manipulate) tuple中的List Object的內容。像是上面這個範例所示,經由函式change_list_content_in_tuple(a)我們就成功更改了tuplea
的內容。但也可以發現雖然我們更改了tuplea
的內容,但是其實tuplea
連結(bind)到的兩個物件 a[0]
和 a[1]
,他們的identity其實還是沒有改變~
所以更嚴謹一點來說,不可變物件中不能更改的是它的連結(binding),並不是連結到的物件的內容不能修改!(更詳細的說明可以看 Mutable vs Immutable Objects 中的 Exceptions in Immutability段落。)
Python如何物件傳遞到函式中?
現在所有需要的基礎知識都湊齊了,終於進入本文的探討重點:Python是如何傳遞物件到函式(function)中的?
Pass by Sharing —Introduction
回到本文開頭提到的三個選項:「Python是pass by value」、「Python是pass by reference」、「Python黑魔法會自動幫我判斷」。其實,Python傳遞參數的方式不是pass by value,不是pass by reference,也不是Python會根據可變/不可變物件幫我們決定pass by reference或pass by value — Python所使用的方法叫做 「pass by sharing」。
Python是純粹的 pass by sharing!
- 也有人把pass by sharing稱作pass by object reference、call by sharing或call by object-sharing,且這種方法被Python, JavaScript, Java(物件類型), Ruby, JavaScript, Scheme, OCaml, AppleScript 等多種語言所使用.
- Pass by sharing這個方式會在函式中製造一個新的變數, 並且讓裡面的新 變數指到和原本(函式外)變數「一樣的物件」。
也就是說:若現在有一個變數a
被指派了一個物件X
(X
的identity是4341515152
)。我們現在要把a
傳到一個函式裡面,此時在函式內會產生一個新的變數a”
用來接收傳入的argument a
,並且將a”
也「指到同個物件X
」(identity一樣是4341515152
)上。
簡單來說就是 — 原本物件X
上有個標籤a
,當我們要把a
傳到一個函式裡,Python會將函式內的另一個標籤a”
也貼在物件X
上。
Pass by Sharing — Integer (Immutable) Object Example
實際用程式來驗證一下這個想法:
def change_x(x):
print(f"[func (before)] x = {x}, id = {id(x)}")
x = 42
print(f"[func (after)] x = {x}, id = {id(x)}")if __name__ == '__main__':
x = 9
print(f"[main (before)] x = {x}, id = {id(x)}")
change_x(x)
print(f"[main (after)] x = {x}, id = {id(x)}")
""" *** output ***
[main (before)] x = 9, id = 4305283712 # same id!
[func (before)] x = 9, id = 4305283712 # same id!
[func (after)] x = 42, id = 4305284768 # changed id!
[main (after)] x = 9, id = 4305283712 # same id!
"""
這個程式範例與前段小節的說明對應:
- main裡面的
x
→ 上述說明裡的a
- main裡面的整數物件
9
→ 上述說明裡的 物件X
- 函式change_x內的
x
(跟main裡面的變數x
不同,這個x是change_x函式內的local variable,換成其他變數名稱也可以) → 上述說明裡的 新變數a”
同樣的邏輯,變數x
作為函式change_x的argument輸入之後,change_x函式內產生的新變數x
也指到「同一個整數物件9
」(可以看到 [main (before)]
和 [func (before)]
時,變數所指到的物件identity都是4305283712
)。
後面重點來了,當我們在change_x函式內把新變數x
改成42
時 (Variable Assignment),我們只是將一個新的整數物件42
指派給新變數x
而已,所以在change_x函式內部並沒有修改(manipulate)從外部傳進來的整數物件9
,且函式內也沒有變數指向9
了 (單純把 9
丟掉換42
)。但是在change_x整個執行完畢之後,函式內變數會消失、外部也無法取得,因為新變數x
是change_x內的local variable,因此在change_x內的x = 42
有做等於沒做,根本影響不到外界~
如果將這個範例的x
換成其他不可變物件(Immutable Objects)也會是一樣的結果。因為不可變物件本身內容並不能被改動(manipulate),所以當這個物件傳入函式之後也不可能被改動到,因此自然無法對Function外部造成影響(除非是前面提到的Exceptions in Immutability)。這也是為什麼很多人會概略地說「不可變物件被傳入Function時,Python會自動使用pass by value」,因為不管在Function內部做什麼都不會影響到外界的x
,看起來就像是pass by value一樣!
雖然傳遞不可變物件看起來像 pass by value,但其實本質上還是有所區別的。Pass by value是會「複製」一份原本的物件再傳入函式,因此在傳遞時會創造一個全新的物件;相對的,pass by sharing並沒有創造新的物件,它只是單純的「也將函式中的 local variable指向原本的物件」。
Pass by Sharing — List (Mutable) Object Example
咦?既然不可變物件傳遞參數機制看起來像是pass by value,那「可變物件的傳參機制就看起來像是pass by reference」嗎?我覺得這樣比喻只答對了一半,讓我們實際用一個可變物件的傳參範例來說明:
這邊我們使用可變物件List Object來做實驗,我現在有三個獨立的List,並且值都是[1,3,5,7],
lst1 = [1,3,5,7]
lst2 = [1,3,5,7]
lst3 = [1,3,5,7]
我設計了三個函式來分別操作這三個List:
- Function 1 (
=
)
def func1(list):
list = [9, 11]
- Function 2 (
+=
,.append()
,.extend()
)
def func2(list):
list += [9, 11]
list.append(50)
list.extend([60,70])
- Function 3 (
=
,+
)
def func3(list):
list = list + [9, 11]
將三個List Object分別傳入三個函式中並印出操作後的結果:
func1(lst1)
func2(lst2)
func3(lst3)print("lst1: ", lst1)
print("lst2: ", lst2)
print("lst3: ", lst3)
在公佈答案前大家可以先想一下最後lst1, lst2, lst3會變成什麼~
— — — — — — — — — — — — — — 防爆雷 — — — — — — — — — — — — — —-
答案是:
lst1: [1,3,5,7]
lst2: [1,3,5,7,9,11,50,60,70]
lst3: [1,3,5,7]
你答對了嗎XD
- Function 1的操作一樣只是變數重新指派而已,並沒有修改
lst1
指到的物件本身的內容。 - Function 2的三個操作都是上一節所介紹的Data Manipulation,它直接修改了「
lst2
這個物件」的內容。更細節來說,func2
中的list
變數跟外部的lst2
變數都是指到同一個[1,3,5,7]
物件,我們在func2
中藉由變數list
來取得這個物件並「修改(manipulate)了這個物件」,最後func2
結束後再從外部藉由變數lst2
重新取得這個已經被修改過後的物件。 - Function 3有趣的地方在於
a += b
跟a = a + b
的差別。a += b
這種寫法會在如果a
是可變物件且支援 In-place 操作的情況下盡量使用data manipulation來更改a
。而a = a + b
這種寫法則不會,所以這邊只是變數指派。(延伸閱讀:Why does += behave unexpectedly on lists?)
所以回到這個先前提出的問題:「Python傳遞可變物件到函式看起來像是pass by reference嗎」?我會說只答對一半的原因在於 — 就算你傳入的是可變物件,但只要你在函式內沒有對這物件做”data manipulation”,一樣無法對函式外部造成影響!只有在函式內部對這個可變物件做”data manipulation”才會對函式外部造成影響。
而 pass by reference的概念是「不管在函式內部對傳入的變數做什麼操作,都會對外界造成影響」。像是:
void pass_by_reference(int& b){
b = 6;
return;
}
這個C++ 函式在內部重新指派了6給變數b
,結果:
...
int b = 1;
pass_by_reference(b);
...
cout << b << endl; // 6 (changed!)
這個變數指派的操作還是對外界的b
造成影響了!跟Python的pass by sharing還是存在根本上的差異~會有人說Python可變物件的傳遞「看起來像是」pass by reference的原因應該是:因為我們在函式內對可變物件的操作較常是“data manipulation”。這個操作會影響到外界,而pass by reference也同樣會影響到外界,所以才有人比喻Python傳遞可變物件比較像pass by reference。
除了前面展示的 Integer Object和 List Object以外,我把其他類型物件的測試結果以及完整的程式放在我的 Github Repository — KY_whatve_u_learned中,有興趣的讀者可以過去逛逛、載下來測試,我就不花篇幅逐一說明了~
講到這邊大家應該就可以理解為什麼前面有個選項是「Python黑魔法會自動幫我判斷」了。的確Python在傳遞可變和不可變物件時看起來有點像兩種不同的傳遞方式,但是了解pass by sharing的細節之後就會發現這只是pass by sharing所造成的「表象」而已;其實不管是可變物件還是不可變物件,它們的傳遞原理根本就是一模一樣的!
不過,就結果來說「Python黑魔法會自動幫我判斷」這個選項雖然看起來很鬧,但還真的是三個選項中最貼近事實的 XD
Recap
最後我們來複習一下本文所討論的pass by sharing重點:
- Python的傳遞參數機制是 “pass by sharing”,和 pass by value, pass by reference存在根本上的差異。
- 函式內部的變數跟函式外部的變數share「同一個」物件,沒有pass by value的「複製」,也不像pass by reference「使用同一個變數」。
- 只有在函式內部對物件做 “data manipulation” 才會對外界造成影響, “variable assignment” 則不會。
後記
花了好幾天終於把這篇文章寫完了。原本以為只需要花這篇1/5的篇幅就能完成的,結果寫著寫著卻發現有很多先備知識需要交(ㄌㄨㄛ)代(ㄙㄨㄛ),就解壓縮成為現在這樣子了…
寫到這突然發現不知如何結尾,再藉機紀錄一下生活好了 — 台大宿舍的冷氣很冷,走出房間時眼鏡起霧讓我看不清視線;我只能努力用摩斯漢堡的熱量與冷空氣對抗,同時看著快寫完的文章說服自己不是拖延症患者。知識好多,世界好大,我還是好怕冷;掰不下去了,草草作結。
感謝點進這篇文章的你,希望你不管有沒有全部看完都能有一些收穫,如果發現本文哪裡需要修正也請不吝嗇地告訴我,最後就祝大家平安快樂。
References
- Variables in Python — Real Python
- Python 到底是 pass by value 還是 pass by reference? 一次搞懂程式語言的函式傳參! — dokelung.me
- Python Variables and Assignment — PLURALSIGHT
- Mutable vs Immutable Objects — Medium
- What does += mean in Python — Stack Overflow
- Parameter Passing — Python Course
- ‘is’ and ‘==’ operators in Python — Net-informations.com
- [C/C++] 指標教學[四]: Pass by value vs Pass by reference — Medium
- Evaluation strategy — Wikipedia