[python筆記] 12. python的 function

前言:

這個系列的筆記來到尾聲,這章主要筆記 function,在之前的筆記裡,有提到方法與函式幾乎是同一個東西,只是一個屬於物件,一個則是單純的函數。
函數對語言來說就是專職於某件事情的機器,就像打蛋器只負責打蛋,果汁機只負責榨汁。通常會將函數指定某項特定的功能,當需要使用該功能的時候,就可以直接呼叫函數。

那麼馬上就來認識函數。

閱讀時間:12 分鐘

認識函數

function 函式又或是函數。在我的觀念裡,它就是最簡單的系統,有輸入、輸出以及處理,因此撰寫函數就必須有這三部分,稱為定義函數。不過函數只有被定義,需要喚醒,而使用它的方式被稱為呼叫函數。

定義函數

想像程式檔案是一個房間,而函數就是房間裡的各種功能性設備,例如:電腦、書櫃、冷氣…,當你想為房間添加設備,打造一個功能,這個過程就是定義函數,增加函數的方式是透過 def 命令實現。

呼叫函數

再說一次,定義函數與呼叫函數是不同的。定義函數就是計畫書,呼叫函數才是真正執行計畫的行動。

函數在程式檔案裡只是程式的存在,就像沒有開機的電腦,靜靜地躺在你的房間,所以需要一些命令來讓它啟動,才會完成你的功能,而這樣的命令與文法,就是呼叫函數,呼叫函數的方式有很多種,一開始先知道最簡單也最常用的就好。
大部分語言呼叫函數的方式是使用 函數名稱 + (),python 也不例外,不過定義函數就有很多不一樣,以下就來看看 python的定義函數與呼叫方式。

python 使用 def 關鍵字來定義函數,而如同其他語言使用函數名稱 + () 呼叫函數。

# 定義函數
def print_hello_twice():
    print("Hello")
    print("Hello")

# 呼叫函數
print_hello_twice()

使用參數與回傳值

函數包含輸入、輸出以及運算,這三部分都與資料相關,輸入的資料稱為參數( parameter );輸出的資料稱為回傳值( return value )。
參數有一些不成文的規定,通常數量不會多於五個,認為當參數多了就容易造成混亂,使功能不明確。而回傳值就有硬性規定,最多只能夠有一個回傳值。可以想像函數就是果汁機,放入許多的水果、牛奶以及冰塊,最後只會生成果汁或冰沙。

那麼參數與回傳值是否都是必要的呢?這個依語言而定,python 認為這兩者都不是必要的,如同上面的範例,就都沒有使用到。因此來看一個都有使用的例子。

# 定義函數
def add(a,b):                     # a與b,都是參數
    if type(a)==type(b):
        return a+b                # a+b 的結果就是回傳值
    else:
        return "型態不同無法相加"  # 字串的文字內容就是回傳值

# 由於程式只會選 if 以及 else 其中一個路徑執行,所以程式裡雖然有兩個 return 都結果一定都是一個回傳值

# 呼叫函數
c=add(1,3)                   # 4
d=add("hello", " world")     # hello, world
e=add(1, "h")                # 型態不同無法相加

簡單的做一個加法運算函數,參數有兩個分別是 a 與 b ,回傳值根據判斷而有不同,但結果都只有一個。

了解函數的結構之後,再深入對於參數、變數以及回傳值進行一些了解。

函數的區塊與變數使用

讓我們再來複習一下區塊的概念,區塊就是一個獨立工作區,每個區塊裡的變數只屬於該區塊。
而函數的處理過程也是建立在新區塊之上,因此讓我們來看看不同區塊的變數,可以如何使用與傳遞。python 有一些很有趣的操作,一起來看看。
以下的例子要好好注意名詞:參數是參數,變數是變數,只有函數( ) 裡的,才可以被稱為參數。

關於參數

參數是將父區塊的資料,給子區塊(函數)來進行使用。看以下的例子:

a,b=1,2
def add_1(x,y):
    x=x+10000
    return x+y

print(add_1(a,b))  # 10003 呼叫函數(函數執行)時,會開闢一個新的空間(子區塊)來執行函數,執行結束後會回收子區塊
print(a)           # 1

從上面的例子,add_1 函數執行時,會由原本的父區塊產生子區塊,子區塊就是執行函數的空間。這個空間裡,有來自父區塊的資料 a 與 b ,是透過參數的方式進入子區塊的( 變數a -> 參數x )。

當 add_1函數執行結束後,會回收子區塊空間,這時我們發現變數 a ,雖然在子區塊裡作為參數 x ,經過 x=x+10000 的運算,卻沒有影響其在父區塊空間的值。因此我們可以想作,函數的參數就像是跟父區塊借了一張白紙,秉持著「有借有還,再借不難」的心態,當函數想要在白紙上畫畫時,為了還給父區塊乾淨的白紙,會選擇依樣畫葫蘆另外做一張白紙,而畫的顏料都塗在 新白紙 上,最後還回去的就會是原本的那張乾淨白紙。

而為了簡單化這個過程,程式的處理方式是這樣的:
在借給子區塊時,會將 變數a 丟給 參數b,當函數要改變 參數b,就會在自己的空間製造一個 變數b ,之後所有的操作都會以 變數b 進行,因此結束後 變數b 的資料即使被改了,也不影響原本的 參數b。

但是,所有的參數都是這樣嗎?好像有一些例外存在!
你可能開始疑惑,到底是怎樣?我們馬上來看下一個例子。

c=[1,2,3]
def add_2(z):
    z[0] = z[0]+1000
    return z[0]+z[1]
print(add_2(c))       # 10003 呼叫函數(函數執行)時,會開闢一個新的空間(子區塊)來執行函數,執行結束後會回收子區塊
print(c[0])           # 10001

從上面的例子,add_2 函數執行時,一樣會由原本的父區塊產生子區塊作為執行函數的空間。這個空間裡,只有來自父區塊的資料 list c ,一樣也是透過參數的方式進入子區塊的。

不過意外的是,list c 序列 0 (c[0]) 在函數裡作為 z[0],經過函數的處理之後,回到父區塊居然仍保持被處理的樣子,那這又是怎麼一回事呢?
這就是 python 有趣的地方,因為 變數c 的 list 其實是一個大空間的入口( 只有大空間才可以裝入很多資料 ),一樣的秉持「有借有還,再借不難」的心態,當函數想要改變參數的資料時,複製了 參數z ,但沒想到的是兩個空間入口居然通向相同的空間,所以當函數更改資料時,也就改了空間裡的資料。

總結兩個例子,函數是個禮貌卻有大神經的人,當函數要改變資料的時候,就會去將資料進行複製來使用,但卻沒有仔細檢查複製的東西,是一張白紙還是一個神秘空間的入口。

python 將像是白紙的型別稱為 immutable 型別,不可變型別;而像是空間入口的型別稱為 mutable 型別,可變型別。

參數資料的型別可變型別不可變型別
型別有list
Dictionary
Set
class
int
float
bool
string
tuple
像是空間入口白紙
在函數中被修改時父區塊的變數也被修改父區塊的變數沒有影響

關於變數

變數是存在區塊中的資料,一般來說各區塊的資料互不相通,所以想要讓 function 能夠使用父區塊的變數,應該就要透過參數進行傳遞。我們來看,如果不使用參數傳遞,強制使用會怎樣?

x,y = 1,2

def sum():
    x=x+y
    return x

print(sum()) # UnboundLocalError: local variable 'x' referenced before assignment

沒有意外,你應該要得到一個 UnboundLocalError 的錯誤訊息,它在告訴你沒有找到與 x 變數關聯的資料。
不過再來看下面這個例子:

x,y = 1,2

def sum():
    return x+y

print(sum()) # 3

咦?怎麼又可以用了?你應該會出現這樣的疑問,這是 python 的機制之一,當子區塊使用的資料是讀取( readonly )時,子區塊沒有找到該變數,就會往上去找父區塊,找到就會取用它。如果父區塊也沒有,就會再往上找爺區塊,當壓根沒有爺區塊時,就會報錯表示沒有找到。

再來,一樣的也考慮一下,如果是 list ,有沒有可能會跟參數一樣,有不一樣的結果呢?所以我們繼續看下一段:

a = [1,2]

def sum():
    a[0]=a[0]+a[1]
    return a[0]

print(sum()) # 3
print(a[0]) # 3

哇!太神奇了!這又是怎麼回事?還記得前面我們把 list 稱為「可變型別」吧,那麼順著這個邏輯思考,當使用父區塊的變數發現是一張白紙,那麼函數害怕弄髒它,就不會去使用,但當發現是空間入口時,就會義無反顧的進去。

看到這裡,你可能已經發現一個秘密了:當面對 可變變數的時候,無論它是作為參數還是父區塊變數使用,都會有一樣的結果。
所以有些工程師使用 python 時,當他希望他的資料肯定能夠隨著 function 做改變,且資料量大又沒那麼重要,就可能會選擇使用這樣的方式,又或是希望回傳的資料不止一個時,也會使用。

結語

這一次的筆記,因為比較多討論了參數與變數在函數的處理,也提到了兩個可能不是那麼容易明白的名詞:「可變型別」與「不可變型別」,所以可能需要多一點的時間消化,另外也想提醒,白紙與空間入口的說法在真正的程式底層,其實並不完全正確,為了好記又能清楚介紹,簡化了不少概念。如果對此有興趣,可以多去查相關資料,但建議你對於資料儲存有所了解在先,不然大機率可能會看得很辛苦。

那麼接下來,就只剩最後一節了,完成 module 後,幾乎已經入門 python ,可以開始自己的練習並往更進步的方向前進。

This article was written by 洋洋. Any similarities to other works are purely coincidental.

我們是 Be Good Tool
團隊由幾位工程師、設計師與PM組成
專注開發各種免費網站工具和推廣優質的APP
歡迎到我們的線上工具列表看看喔!