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

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

文章瀏覽次數 3524

特種兵

特種兵圖像(預設)

2020-07-25 15:30:15

From:1.161.146.237

第三章 函數

3-1 函數呼叫

這裡的 函數 (function) 跟數學函數不一樣,有人也把函數叫做 函式

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

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

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

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

其實,我們在前面的章節已經呼叫過函數,像是列印及查看資料型態。如下:

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

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

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

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

有人把參數稱為引數,這邊我們先不深究參數跟引數的差別的問題。

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

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

它是安裝 python 直譯器時就有的,而且看不到它的原始程式碼。

3-2 型態轉換函數

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

# 例子一
>>> int('32')
32
# 例子二
>>> int('Hello')
ValueError: invalid literal for int(): Hello

第二個例子,因為字母字串不能轉成整數,所以會產生錯誤。

轉成整數是指把數字字串外的引號去掉,變成真正的純數字,轉換後可以拿來做四則運算。

在電腦的世界裡,每個字元都有一個代表自己的唯一值,可以稱為內碼,也就是字元的編碼。

像是 ascii 或 unicode 都是一種編碼,例如 A 的 ascii 編碼是 65

這邊只是要提醒大家,字串轉整數並不是將字元轉回原始編碼。

另外,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() 函數與 int(), float() 函數的作用正好相反,是把純數字轉換成字串。

值得注意的,當浮點數的數值太長時,後面到一定位數就會被四捨五入,原因是每個資料形態都有記憶體的上限,所以上限到了,轉成字串也會把後面的數字省略。因此有可能不精確,但在一般應用上問題不大。

從上述幾個型態轉換函數的範例中,我們也觀察到,任何型態當作 str() 的參數來轉成字串,通常不會發生錯誤,但想要轉成整數或浮點數,如果參數是轉不了的字元就會報錯。

3-3 數學函數

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

>>> import math
>>> print(math)
<module 'math' (built-in)>
>>> import trace
>>> print(trace)
<module 'trace' from 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python39\\lib\\trace.py'>

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

沒有引入就使用的話,python 會找不到它們,然後就會發生錯誤。

# 假設我沒有先 import math
>>> print(pi)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'pi' is not defined
>>> print(math.pi)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'math' is not defined

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

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

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

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

另外,關於 math, trace 這兩個模組,import 進來後,是不是有發現印出來的資訊不太一樣。

math 是 built-in ,也就是標準函數,而 trace 則是外部函數,通常外部函數會有 py 檔可以查看原碼。它可能是 python 直譯器自帶的,雖然不是內建,但相對重要的函數,也可能是我們自己使用 pip 工具或其他方式安裝進來的。

如果是我們自己寫的函數,稱為自定函數,當然也是外部函數的一種。

3-4 組合

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

>>> import math
>>> circle = 10
>>> x = math.sin(circle * math.sqrt(4) * 5)
>>> x
-0.5063656411097588
>>> y = math.cos(math.log(x+1))
>>> y
0.7609889586512912

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

>>> import math
>>> x = y = 2
>>> type(x, y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments
>>> help(type)
...
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type
...
>>> math.sin(120, 200)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: math.sin() takes exactly one argument (2 given)
>>> math.sin('120')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be real number, not str

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

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

>>> hours = 10
>>> 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 的語法,並不是只為了排版好看而已。

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

因為 python 不使用大括號來當作區塊的開頭與結尾,所以必須另外使用一種方式讓 python 直譯器了解哪個範圍是這個函數的定義區塊。只要是同一個區塊都必須縮排,像是函數、迴圈、if判斷式與 with 語法等,並且允許多層縮排。

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

如果是空的一對小括號 () ,表示該函數沒有任何參數。至於字串到底要用單引號還是雙引號,多數人用單引號,有人說因為可以少按 shift 鍵。

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

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

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

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

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

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

3-6 定義與用途

我們剛剛定義了函數,值得一提的是,函數在被呼叫前一定要先完成定義。也就是要先完成 def 的函數定義後,才可以去呼叫這個函數。

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

printHello()
def printHello():
	print('hello')
# Traceback (most recent call last):
#   File "D:\test.py", line 1, in <module>
#     printHello()
# NameError: name 'printHello' is not defined

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. 詳解:當參數為 'hello' 字串時,會在畫面印出兩行 hello, 也就是帶入什麼就會在畫面上印出兩次什麼,可以把 arg 想成是一個代名詞

範例中,我們結束函數定義後,連續呼叫兩次 printTwice,

第一次帶入 hello 字串參數。

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

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

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

3-9 區域變數

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

當函數結束後,該變數跟著消失,應該說 python 把它回收了。

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

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

這個函數有兩個參數,在函數本體中有一個 cat 變數,它的內容是帶進來的兩個變數相結合,然後呼叫 print() 把 cat 變數的值印出來。

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

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

我們帶入了 'abc' 與 'def' 兩個字串,最後印出 abcdef 這個字串。

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

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

def catTwice(part1, part2):
	cat = part1 + part2
	print(cat)
line1 = 'abc'
line2 = 'def'
catTwice(line1, line2)
print(cat)
# Traceback (most recent call last):
#   File "D:\test.py", line 5, in <module>
#     print(cat)
# NameError: name 'cat' is not defined

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

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

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

3-10 堆疊

堆疊存放著這些變數與函數,以上例而言,catTwice 被 python 呼叫,也就是 __main__ 所呼叫。

__main__ 是一個特別的名稱,它屬於 python 最頂層。

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

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

3-11 返回值

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

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

雖然有印出資料,但並沒有回傳值,print() 只是其中的一行敘述。假如沒有使用 print() 的話,那就真的什麼都沒有了。

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

大家有注意到嗎?在交互模式下,執行完有回傳值的函數,不需要使用 print 函數,就可以看到返回函數的返回值,但在腳本模式使用 python 直譯器執行時,它不會自己顯示在畫面上,我們在上一章已經討論過。

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

def printTwice(arg):
	print(arg)
	print(arg)
result = printTwice('你好')
print(result)
# 你好
# 你好
# None

函數執行印出兩次「你好」後就結束了,我們把函數回傳的值設定給 result 變數,然後印出它的值是 None

None 不是字串,它是一個特別的值,實際上就是一種資料型態。同時也不能使用 None 這個字來命名變數或函數。

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

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

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

3-12 為什麼要使用函數

歸納以下四點供參:

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

3-13 導入(引入) import

從前面的章節,我們已經知道基本的導入模組與使用該模組下的函數或變數。

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

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

假設在兩個不同的模組中,剛好都有一個叫 sin() 的函數,當同時引入這兩個模組的所有函數後,在程式使用 sin() 到底是指哪一個模組的 sin() 呢?因此應該儘量避免這樣的引入方式。

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

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

在這個例子中, m 就是 math 了。這個功能對於模組名稱很長的情況非常方便,但還是要考慮一下可讀性。

除錯

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

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

  1. 每層一個 tab 會比較簡潔。以後你的程式會越來越複雜,縮排到五或六層都有可能,如果一層用四個空格的話,那閱讀起來(摸讀點字)會很辛苦,效率不彰。

    我們的點顯器通常是40方,如果有很多層縮排,閱讀程式碼時,必須經常左右捲動點字視窗。單純用聽的,則須計算或記住空格的數目,才能確定縮排層次,有點煩人。若使用純文字編輯器,縮一層一次要按四個空白鍵,也似乎有點辛苦。

  2. 逐字移動時,導讀軟體可以直接朗讀 tab 這個符號,所以不會把它跟一般空格搞混。

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

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

我們也可能在網路上複製程式碼回來修改或執行,在網頁中的程式碼經常會出現全形空白或一些奇怪的符號,執行時可能發生錯誤,因此一定要徹底檢查程式碼,利用編輯器的功能,一次取代掉不正確或不需要的符號。

動動腦

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

arg1 = 'abc'
print(arg1)

def func1():
	arg1 = 'hi'
	print(arg1)

func1()
print(arg1)

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

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

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

arg1 = 'abc'
print(arg1)

def func1():
	global arg1 
	arg1 = 'hi'
	print(arg1)

func1()
print(arg1)

請參考本章後面的資料。

練習

範例

寫一個函數名為 circleArea, 接受一個半徑為參數 r, 計算圓面積並印出結果,請見影片實做。

第1題 檔名 3-1.py

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

>>> len('hello')
5

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

從左邊起算往右邊第 20 格是這個參數的最後一個字母。執行結果如下:

>>> rightJustify('allen')
               allen

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

第2題 檔名 3-2.py

請設計一個函數,名稱自訂,可以帶入兩個參數,參數的值可以靈活改變:

  • 第一個參數:蛋糕的數量
  • 第二個參數:每個蛋糕的價格

功能是計算買了幾塊蛋糕共需要多少錢,執行範例參考:

buyCake(3, 100)
您買了 3 塊蛋糕,共需 300 元。
第3題 檔名 3-3.py

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

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

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

補充

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

參考資料

python入门:什么是函数

python global

python 區域變數與全域變數

對數與指數操作

影片

第三章 函數 part one

第三章 函數 part two

最後更新:2021-10-08 22:31:12

From: 111.249.165.250

By: 特種兵