[python] [VI coding] 第三章 函數 - 教學區

[python] [VI coding] 第三章 函數

特種兵

特種兵圖像(預設)

2020-07-25 15:30:15

From:1.161.146.237

第三章 函數

3-1 函數呼叫

這裡的 函數 跟數學函數不一樣,有人也把函數叫作 函式

函數是組織好的、可重複使用的、用來實現單一或相關功能的程式區塊。

我們可以使用 python 已經定義好的函數(內建函數),也可以引入函式庫然後使用相關函數,當然也可以自訂函數來應用。

對程式來說,使用函數就是呼叫函數。

在這份講義中,我們目前先將建構子包含在內建函數當中,等提到 class 再來研究。

事實上,我們在前面的章節已經呼叫過函數,如下:

>>> type(10)
<class 'int'>

利用上面這個例子,我們可以來分析一下函數的架構。

  1. 函數名稱: type
  2. 函數參數: 10
  3. 函數傳回值 <class 'int'>
  4. 函數功能: 回傳參數的資料型態

函數的特徵就是在函數名稱之後有一對 小括號 (),如果有參數就會放在小括號當中。

有人把參數稱為引數,這邊我們先不深究參數跟引數一不一樣這個問題。

這個 type 函數是 python 定義好的函數,我們也把這樣的函數稱為 內建函數

也就是你不需要先手動引入函式庫就可以隨時隨地直接呼叫它來使用。

3-2 型態轉換函數

python 提供一些內建函數作為資料型態的強制轉換,例如轉換成整數的函數:

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello

第二個例子因為字母字串不能轉成整數,所以會產生錯誤。這與把字元轉回 ascii code 是不同的概念。

簡單說,轉成整數就只是很單純的將數字字串直接去掉引號變成純數字的概念,跟字元的編碼無關。

另外,int() 將小數轉換成整數會無條件捨去小數。

>>> int(3.99999)
3
>>> int(-2.3)
-2

float() 將整數或字串轉換為浮點數:

>>> float(32)
32.0
>>> float('3.14159')
3.14159

例子一中, 32 本來就是整數沒有小數,想轉成小數也就是多個 .0 而已。

最後,str() 則將參數的型態轉為字串:

>>> str(32)
'32'
>>> str(3.14159)
'3.14159'

str 跟之前函數的作用正好相反,他把純數字轉換成字串。

值得注意的,當浮點數的數值太長時後面到一定位數就會被四捨五入,原因是每個資料形態都有記憶體的上限,

所以上限到了,轉成字串也會把後面的數字省略。因此有可能不精確,但在一般應用上問題不大。

3-3 數學函數

python 有數學函數的模組,也可以稱為函式庫,使用這些函數前必須先導入該模組,模組裡面除了函數也可能會有系統變數。

>>> import math
>>> print(math)
<module 'math' (built-in)>

這與之前介紹的 type 函數不同,想使用一定要先引入該模組才會有該函數,因為該函數定義在該模組之下。

沒有引入的話,python 會找不到他們。

預使用 math 模組裡的數學函數或變數,就必須在呼叫函數前加上模組名稱和一個小數點,當然這也跟函數定義在 math 模組之下有關。

我們可以把模組想成是函數的大集合包,只要引入了模組就可以使用裡面的資源。

>>> n = 10
>>> math.sin(n)
-0.5440211108893698
>>> math.pi
3.141592653589793
>>> math.sqrt(100)
10.0

有興趣這些函數的功能可以自己 google 一下,這邊先不深究數學演算的部分。

3-4 組合

多個函數是可以組合的,也就是可以混合使用,只要符合函數本身的需求或規範即可:

x = math.sin(degrees / 360.0 * 2 * math.pi)
x = math.exp(math.log(x+1))

函數可以一個個這樣套上去,只要函數本身參數與回傳值都是正確的即可。

通常一個敘述中,放值的地方都可以使用函數,當函數層層疊疊時,我們最好從裡而外開始解讀一層又一層的函數,

賦值的左邊通常都是放一個變數來得到函數的回傳值。

>>> minutes = hours * 60                 # right
>>> hours * 60 = minutes               
SyntaxError: can't assign to operator

例子二為什麼錯了,因為最左邊是一個運算式,而不是一個變數,簡單說,最左邊不能是有運算子的式子。

3-5 定義函數

之前都是使用 python 定義好的函數或引入其他模組後調用該模組定義好的函數。

其實我們也可以自訂函數,如下:

def printHello():
	print('hello')

這個自訂函數的功能是在畫面上印出 hello, 它呼叫了 print 內建函數來完成這個工作。

def 是一個關鍵字,它用來定義函數。冒號表示這個函數定義標題結束,冒號後面則是定義內容。

printHello 是函數名稱,這邊我們先以駝峰式命名法來為函數命名。

還有個關鍵就是定義內容必須縮排,這是 python 的語法,並不是只為了排版好看而已。

通常縮排(亦稱縮進)是四個空格或一個 tab 鍵。

因為 python 不使用大括號來當作區塊的開頭與結尾,所以必須另外使用一種方式讓 python 直譯器了解哪個範圍是這個函數的定義區塊。

所以只要是同一個區塊都必須縮排,像是函數、迴圈、if判斷式與 with 語法等,並且允許多層縮排。

函數的命名規則與變數相同,雖然函數名稱可以與變數名稱相同,但盡量避免這種狀況。

而空的小括號表示該函數沒有任何參數。

至於字串到底要用單引還是雙引,多數人用單引,有人說因為可以少按 shift 鍵。

查了一些程式語言的規範,除了特殊的語言規定外,單純的字串建議用單引號。但如果遇到第二層引號就會把第二層改成雙引。

雖然可以使用連續單引,但必須使用跳脫字元 \ (返斜線) 但總是比較麻煩,看起來也比較亂。

接下來呼叫自訂函數的方式就跟呼叫系統內建的函數一樣。也可以使用 type 來確認函數的型態。

如果是在 python 終端定義函數,在出現三個小數點的地方是提醒我們要完成函數的定義,想結束定義則輸入空白行也就是按 enter 即可。

函數被定義後,你可以在任何地方使用他,甚至是其他的函數中也可以。

當然需要在同一個程式中或者需要跟之前介紹的方式一樣引入它來使用。

3-6 定義與用途

我們剛剛定義了函數,值得一提的是,函數在被呼叫前一定要先完成定義。

也就是先要有 def 的函數定義完成後,才可以去呼叫這個函數。

你可以試看看如果使用未定義的函數,或者把定義放在呼叫之後,會有什麼錯誤訊息。

3-7 執行順序

執行函數並不會改變程式的執行順序,他只是跳到定義函數的地方去執行函數定義,也就是函數的本體。

所以函數雖然被定義了,但在還沒有被呼叫前,定義都不會被執行,也就是不會被實體化。

我們已經知道函數調用前需要先定義它,因此我們在閱讀程式碼時不一定要完全按照從上到下逐行讀取程式內容,

如果依據程式的執行序來閱讀程式可能會更好,也就是把自己想成是 python 直譯器,根據你的程式執行序來閱讀程式碼。

例如你先不讀定義,而是在要呼叫這個函數時再往回看它的定義。

想要先看完所有函數的定義再看整個執行流程也可以,這與每個人閱讀程式碼的習慣有關。

3-8 參數

有的函數不需要參數,有的函數需要傳入多個參數。

多個參數之間使用逗號來隔開。

我們必須弄清楚什麼是一個,什麼是多個參數。

看一下這個例子:

def printTwice(arg):
	print(arg)
	print(arg)

printTwice('hello')
printTwice('hello' * 4)

解釋一下這個函數:

  1. 函數名稱:printTwice
  2. 函數參數個數:1
  3. 函數參數名稱:arg
  4. 函數屬性:自訂函數(def)
  5. 函數功能(函數本體):在畫面上印出兩次 arg, 它呼叫兩次 print 內建函數來完成這個工作
  6. 詳解:當參數為 'python' 字串時就會在畫面印出兩行 python, 也就是帶入什麼就會在畫面上印出兩次什麼,可以把 arg 想成是一個代名詞

然後我們結束函數定義後連續呼叫兩次 printTwice, 一次帶入 hello 字串參數,

第二次帶入 'hello' * 4 作為參數,這是我們要討論的重點。

其中在最後一行,我們的參數是 'hello' * 4 展開後也就是 'hellohellohellohello' 這只是一個字串,也就是一個參數而已。

最簡單的判斷方式就是逗號,空的小括號就是沒有參數,沒有逗號就是一個參數,一個逗號就是兩個參數,兩個逗號就是三個參數,以此類推。

3-9 區域變數

在函數本體中建立變數時,表示這個變數的作用域僅在這個函數當中,這就叫作區域變數。

當函數結束後,該變數也就跟著消失。

請看以下我們定義一個函數:

def catTwice(part1, part2):
	cat = part1 + part2
	printTwice(cat)

這個函數有兩個參數,在函數本體中有一個 cat 變數,它的內容是帶進來的兩個變數相結合。

然後呼叫剛剛定義的 printTwice 把 cat 當成參數帶進去,

所以最後會在畫面上印出兩次 cat 的內容。這次我們的重點放在 cat 這個區域變數。

讓我們呼叫這個函數來觀察一下:

>>> line1 = 'abc'
>>> line2 = 'def'
>>> catTwice(line1, line2)
abcdef
abcdef

我們帶入了 'abc' 與 'def' 最後印出兩次 abcdef

這個 cat 變數只存活在 catTwice 函數之中,當函數結束後,變數也隨之消滅。

我們試著在 catTwice 函數外印出它:

>>> print(cat)
NameError: name 'cat' is not defined

因為 cat 在 printTwice 結束後就沒了,所以系統告訴我們 cat 沒有被定義。

同樣的,part1 跟 part2 也是屬於該函數中的區域變數。

在函數之外想印出 part1, part2 一樣會有錯誤。

3-10 堆疊

堆疊存放著這些變數與函數,以上例而言, printTwice 被 catTwice 呼叫,

而 catTwice 被 __main__ 所呼叫。__main__ 是一個特別的名稱,他屬於最頂層。

也就是說,一個變數定義在所有函數之外的話都是屬於 __main__

當函數呼叫發生錯誤時,python 會由上而下一層層的依序列出問題。

你可以試著在主程式直接使用未定義的 cat 變數,或在函數中呼叫未定義的變數以觀察其錯誤訊息。

3-11 返回值

一個函數有返回結果時,我們稱它為返回函數。

另一種沒有回傳值的像 printTwice 稱為空返回函數,它們執行結束後什麼都沒留下來。

而有返回值的函數,你通常會利用這個值再去做些什麼,例如賦值給其他變數,或者用於其他運算式中。

大家有注意到嗎?在交互模式下,你不需要使用 print 函數就可以看到返回函數的返回值,但在檔案中使用 python 直譯器執行時,它不會自己顯示在畫面上。

如果使用一個變數來取得沒有返回值的函數的返回值,則會回傳 None 這個值給該變數。

>>> result = printTwice('Bing')
Bing
Bing
>>> result
None

函數執行印出兩次 Bing 後就結束了,而 result 變數接到的值是 None

None 不是字串,它是一個特別的值,事實上它自己就是一種資料型態。

同時也不能使用這個字來命名變數或函數。

所以我們可以知道,沒有返回值的函數,嚴格說起來其實是會返回 None 的。

>>> print(type(None))
<class 'NoneType'>

目前我們撰寫的函數範例都沒有返回值,在後續章節我們會寫出更有用且有返回值的函數。

3-12 為什麼要使用函數

歸納以下四點供參:

  1. 函數用來群聚相同功能的程式碼,增加程式的可讀性且較容易除錯。
  2. 函數可以讓原本的程式碼更精簡,並且確保重複的區塊可以一再使用,想調整時只要修改函數的內容區塊即可。
  3. 把程式依功能拆分成大大小小的函數,將函數組織起來就會成為完整的程式,這些函數可以依照實際的需求做不同的組合。
  4. 有用的函數一旦完成,在各個程式都可能重複利用,可以讓你更快速地把程式寫好。

3-13 導入

python 提供兩種方式導入模組,這個動作也可以說 引入載入。我們已經看過其中的一種:

>>> import math
>>> print(math)
<module 'math' (built-in)>
>>> print(math.pi)
3.14159265359

當你載入 math 模組,就可以使用 math 這個模組當作物件的名稱來使用其下的變數或函數。

但如果你不引入 math 就直接使用 pi 則會發生錯誤:

>>> print(pi)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'pi' is not defined

接下來,有一個替代方案,你可以直接指定導入哪一個模組的哪一個函數或變數,如下:

>>> from math import pi

利用這樣的引入方式,現在你可以直接使用 pi 而不使用小數點符號:

這樣的方式會更明確,在一開始就可以知道會用到哪些函數。

>>> from math import pi
>>> print(pi)
3.14159265359

你也可以使用 * (星號) 來載入該模組下的所有函數及變數定義:

>>> from math import *
>>> cos(pi)
-1.0

這樣直接導入的優點是讓你的程式碼更簡潔,缺點則是如果有相同名稱的函數或變數會產生衝突。

最後,你還可以使用 as 來指定模組名稱的別名:

>>> import math as m
>>> m.sqrt(100)
10.0

在這個例子中 m 就是 math 了

除錯

在程式縮排方面你可能會遇到問題,有人喜歡用空格、有人則習慣用 tab,但有個重點是請統一用一種,不要兩種混用。

這邊我特別說一下,書上建議四個空格,但我建議全盲視障者用 tab 來縮排,縮一層用一個 tab 鍵,理由有二:

  1. 每層一個 tab 會比較簡潔:
    以後你的程式會越來越複雜,可能縮排到五或六層都有可能,

    如果你一層用四個空格的話,那你閱讀起來(摸讀點字)會很辛苦,效率不彰,

    通常我們的點顯器是40方,如果有很多層縮排,這樣在閱讀程式碼時你必須經常左右捲動點字視窗。

    如果單純用聽的必須計算或記住空格的數目才能確定縮排層次也有點煩人。

    然後一次要按四次空白鍵,我覺得有點多。

  2. 導讀軟體可以直接朗讀 tab 這個符號,所以你不會把它跟空白搞混,這一點對明眼人來說以視覺的方式反而容易弄錯。

最後,還要提醒各位如果使用腳本方式撰寫程式,也就是把程式碼存在檔案裡,記得時時存檔。

尤其是在編輯器直接可以結合 python 出現執行結果的狀況下,記得每次執行程式前只要對程式有過修改都必須先存檔才會看到最新的執行結果。

我們也可能在網路上複製程式碼回來修改或執行,在網頁中的程式碼經常會出現全形空白或一些奇怪的符號,執行時可能發生錯誤,

因此程式碼一定要整個檢查,利用編輯器的功能一次取代掉不正確或不需要的符號。

動動腦

如何在函數中使用函數之外定義的變數?

arg1 = 'abc'

def func1():
	arg1 = 'hi'

print(arg1)

上面只會印出 abc 沒有印出 hi 是因為在 func1 函數定義的 arg1 在 func1 是區域變數,func1 結束後就沒了,

因此在 func1 之內想變更全域變數 arg1 的值,這時候我們需要一個關鍵字 global,

在函數中使用 global 讓程式知道我們想改變的是全域變數而非區域變數。

請參考後面的資料。

練習

第1題 檔名 3-1.py

python 有一個內建函數名為 len()。這個函數接受一個型態為字串的參數並且回傳它的長度,如下:

>>> len('hello')
5

我們想建立一個名為 rightJustify 的函數,它同樣接受一個字串參數,沒有回傳值,功能是把這個字串靠右印出,

從左邊起算往右邊第 70 格是這個參數的最後一個字母。如下所示:

>>> rightJustify('allen')
                                                                 allen

不要把函數寫死,也就是傳入的參數如果是 orange 或者 pythonpython 都要可以正確顯示。

第2題 檔名 3-2.py 檔名 3-3.py

我們定義了一個函數 doTwice 接受一個參數,功能是連續以函數的方式呼叫這個參數兩次,像這樣:

def doTwice(f):
	f()
	f()

def printSpam():
	print('spam')

doTwice(printSpam)
  1. 把上面這段程式寫進檔案並執行測試看看有沒有問題
  2. 改寫 doTwice 函數,接受兩個參數,一個是函數,另一個是字串,功能一樣是以函數的方式呼叫第一個參數兩次,但必須把字串作為第一個函數參數的參數來呼叫
第3題 檔名 3-4.py

寫一個可以列印網格的函數,如下所示:

>>> printGrid('|', '-')
|   |   |
| - | - |
|   |   |

>>> printGrid('h', 'q')
h   h   h
h q h q h
h   h   h

補充

  • print 函數每次結束會自動幫我們輸出換行,如果不希望換行可以這樣寫: print('hello', end = '')

參考資料

python入门:什么是函数

python global

python 區域變數與全域變數

對數與指數操作

最後更新:2020-08-09 23:09:28

From: 1.161.139.104

By: 特種兵