案情不單純 Python (1)— How objects are passed to Functions

KY
KY! 你到底學了什麼
28 min readSep 21, 2020

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 valuepass by reference 這兩種。

  • Pass by value: 當一個參數是 pass by value,對於參數而言,呼叫與被呼叫者是獨立的變數,程式會複製一份與呼叫者相同的值給被呼叫者。因此若在函式內部改動被呼叫者,在外界的呼叫者並「不會」受到影響
  • Pass by reference: 當一個參數是 pass by reference,呼叫與被呼叫者,都使用相同的變數。因此若在函式內部改動被呼叫者,在外界的呼叫者「會」受到相同的影響

下面是簡單的C++例子,可以看到變數ab分別使用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跟第一行我們所指派給變數a10應該要是不同的物件才對啊?同理,第四行程式也有一樣的問題。會產生這樣結果的原因就是這一個小節所提到的”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罷了。

a = a + 1 只是變數指派而已!

如果有看 “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時並沒有新的物件被創造出來,而是直接修改原有物件的「內容」。

當然可變物件的操作也不一定要是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)上。

Function中的新變數 a’’ 也和外部變數 a 指到同一個物件 X

簡單來說就是 — 原本物件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 += ba = 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” 則不會。
比較3種不同傳遞參數策略在Function中做variable assignment/data manipulation是否會影響外界

後記

花了好幾天終於把這篇文章寫完了。原本以為只需要花這篇1/5的篇幅就能完成的,結果寫著寫著卻發現有很多先備知識需要交(ㄌㄨㄛ)代(ㄙㄨㄛ),就解壓縮成為現在這樣子了…

寫到這突然發現不知如何結尾,再藉機紀錄一下生活好了 — 台大宿舍的冷氣很冷,走出房間時眼鏡起霧讓我看不清視線;我只能努力用摩斯漢堡的熱量與冷空氣對抗,同時看著快寫完的文章說服自己不是拖延症患者。知識好多,世界好大,我還是好怕冷;掰不下去了,草草作結。

感謝點進這篇文章的你,希望你不管有沒有全部看完都能有一些收穫,如果發現本文哪裡需要修正也請不吝嗇地告訴我,最後就祝大家平安快樂。

References

  1. Variables in Python — Real Python
  2. Python 到底是 pass by value 還是 pass by reference? 一次搞懂程式語言的函式傳參! — dokelung.me
  3. Python Variables and Assignment — PLURALSIGHT
  4. Mutable vs Immutable Objects — Medium
  5. What does += mean in Python — Stack Overflow
  6. Parameter Passing — Python Course
  7. ‘is’ and ‘==’ operators in Python — Net-informations.com
  8. [C/C++] 指標教學[四]: Pass by value vs Pass by reference — Medium
  9. Evaluation strategy — Wikipedia

--

--