第五章 條件與遞迴
5-1 取餘數運算子
比較常見的運算子除了之前提過的 +, -, *, / 四則運算以外,在程式中通常會有一個取餘數的運算子。
在 python 中是 % (百分比) 符號,它的語法規則和其他的運算子一樣,功能則是取得第一個整數除以第二個整數的餘數。
在數學中沒有這種運算子,所以我們特別提出來說明。
>>> quotient = 7 // 3
>>> quotient
2
>>> remainder = 7 % 3
>>> remainder
1
所以,7 除以 3 是 2 餘 1
這邊我們用了取整數的除法運算子 // 才不會有小數。
取餘數非常實用,例如判斷 a 取餘數 b 若為 0 ,則表示 a 整除 b。
利用取餘數可以很快地取得右邊位數的數字,例如 a % 100 可以取得 a 的十位與個位數(如果有的話)。
5-2 布林運算式
布林運算式使用 == (比較運算子,也有人稱為關係運算子) 來判斷一個式子的結果。
結果為真 (True) ,則條件成立,執行你想做的事情;反之,結果為假 (False) ,則條件不成立,執行不成立時想做的事情,或者什麼都不做。
>>> 5 == 5
True
>>> 5 == 6
False
True 跟 False 是兩個特別的值,它們屬於 bool (boolean) (布林資料形態),不是字串,同時它們的首字母必須是大寫。
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
如果我們使用全小寫,會發生錯誤:
>>> type(true)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'true' is not defined
python 以為我們有個叫做 true 的變數,但實際上沒有定義,所以它找不到而回報錯誤。
== 是比較運算子的其中之一,列出其他的如下:
- x != y # x 不等於 y,不等於又可寫成 <>
- x > y # x 大於 y
- x < y # x 小於 y
- x >= y # x 大於或等於 y
- x <= y # x 小於或等於 y
這些符號都很容易理解,但經常發生的錯誤是把 == 寫成 =。
兩個等於是比較運算子,一個等於是指派運算子。
比較運算子用來比較兩個運算元之間的關係,而指派運算子是賦右邊的值給左邊的運算元。
>>> a = 3
>>> b = 5
>>> a != b
True
>>> a == b
False
>>> a
3
>>> b
5
>>> a = b
>>> a
5
>>> b
5
>>> a == b
True
>>> b = 10
>>> a
5
a 雖然等於 b, 但並不會因為 b 後來的值改變而跟著改變。
5-3 邏輯運算子
有三個邏輯運算子分別為 and (且,也就是和) or (或) 與 not (非,也就是否、不是)。
它們的意思也很明確,例如: a > 0 and a < 10
結果為真,
那麼 a 的值只可能介於 0 和 10 之間,也就是 1 到 9 的其中一個數。(這邊先假定 a 只能是正整數)
因為 0 不大於 0 ,且 10 不小於 10 ,所以 0 跟 10 都不在 a 的範圍當中。
又如: n % 2 == 0 or n % 3 == 0
結果為真,表示 n 可能整除 2 也可能整除 3,甚至兩者都整除也可以。按照順序,當有一個條件為真,則結束判斷。
最後, not 的例子,not (a < 10)
條件為真,就表示 a >= 10, 可以把 not 想成 相反 的意思。
對 python 來說,比較運算式不是很嚴格,也就是非零的值,都會被當成 True 來看待。
>>> 17 and True
True
有時可以善用這種靈活性,但你必須了解你在做什麼,否則就避免這樣的用法。
5-4 條件的執行
為了寫出有用且靈活的程式,我們經常需要運用條件判斷與執行相應的敘述,或改變程式的行為。
最常使用的就是 if (如果) 敘述:
if 條件:
執行程式
我們看一下實際的例子:
if x > 0:
print(x, '是正數')
如果 x 大於 0 條件為真,就執行後面縮排的敘述,否則什麼事都不會發生。
if 的敘述跟函數定義的結構相似,具有一個標題與一個縮排的程式區塊。
縮排的程式碼沒有限制多少行的數量,但至少要有一個敘述,你也可以使用 pass 來跳過 if 敘述,它不會做任何事。
pass 通常用來架構程式碼,類似虛擬碼 (pseudo code) 的作用。
或者 if 區塊內還沒想好裡面完整的程式碼,但已經知道要完成什麼事了,所以可以先放一段註解進去,之後再把程式碼填上。
if x < 0:
pass # 需要處理負數行為
5-5 替代執行
if 敘述的第二種形式是二擇一的,同時程式只會執行一個區塊,像這樣:
if 條件:
執行程式
else: # 其他條件
執行程式
直接看實際的例子:
if x % 2 == 0:
print(x, '是偶數')
else:
print(x, '是奇數')
當 x 可以整除 2 就表示它是偶數,否則為奇數。程式會印出相應的訊息。
替代執行可以說是二擇一的條件執行,隨著條件,只可能發生一種結果,因為真與假本來就是對立的兩面。
這種結構又被稱為 分支敘述,類似樹狀的結構。如果出現這樣的敘述,無論如何一定會有其中一部分被執行。
5-6 帶狀條件
有時我們會需要兩種以上的條件來處理可能的情況,這就是 帶狀條件。
if 條件1:
執行程式
elif 條件2:
執行程式
elif 條件3:
執行程式
else: # 其他條件
執行程式
看一下實際的例子:
if x < y:
print(x, '小於', y)
elif x > y:
print(x, '大於', y)
else:
print(x, '和', y, '是相等的')
elif 就是 else if 的縮寫。它可以說是一個 if 敘述的分支,沒有限制它的數量。
事實上 elif 或 else 在 if 敘述中是非必要的。但如果有 else 的話,一定要放在最後。
if choice == 'a':
drawA()
elif choice == 'b':
drawB()
elif choice == 'c':
drawC()
當第一組 if 條件為假時,繼續測試 elif 的條件,直到一個條件為真,則執行該條件下之縮排敘述。
如果沒有 else ,則整個帶狀的 if 條件可能都不會被執行,但若有 else 條件,則該帶狀條件必有一個敘述會被執行。
當一個條件為真時,執行完其縮排程式碼區塊後,就會離開整個帶狀的 if 條件。
如果有多個條件為真,則按順序只執行第一個為真的條件。
沒有 if 就不會有 elif 或 else, 它們不會先於 if 出現。
5-7 巢狀條件
一個條件可以放於另一個條件之中,就是所謂的 巢狀條件,又稱嵌套條件。例如:
if x == y:
print(x, '與', y, '相等')
else:
if x < y:
print(x, '小於', y)
else:
print(x, '大於', y)
首先有兩個條件,一個是 x == y
,另一個是其他狀況。
當其他狀況發生時,又分成兩個條件,也就是大於或小於。
如果可以的話,少用太多層的巢狀條件,因為它看起來不能快速被理解。而且最好要加註解,讓閱讀者知道這段判斷是做什麼用的。
接下來是一個改寫巢狀條件的例子:
if 0 < x:
if x < 10:
print(x, '是個正個位數')
可以改寫成:
if 0 < x and x < 10:
print(x, '是個正個位數')
對於這種條件,python 有個更簡潔且更像數學式的寫法如下:
if 0 < x < 10:
print(x, '是個正個位數')
另外,當判斷條件是真的狀況,可以這樣縮寫判斷式:
is_num = True
if is_num:
print('它是真的')
else:
print('它是假的')
print('程式結束')
上例中,最後一行程式無論如何一定會被執行到。
5-8 遞迴
在前面的章節中,我們知道函數中可以呼叫另一個函數,當然函數也可以呼叫自己本身,這就叫做「遞迴」。
它可以讓我們做出很神奇的事,例如這個函數:
def countDown(n):
if n <= 0:
print('爆炸')
else:
print('還有', n, '秒會爆炸')
countDown(n - 1)
當 n 小於或等於 0 時印出爆炸,否則就印出還有多少時間會爆炸,並且以 n - 1 作為參數,再次呼叫自己這個函數。
我們像這樣調用這個函數:
>>> countdown(3)
函數的執行過程如下:
- 當 n 是 3, 它大於 0, 因為符合 else 的條件,所以印出還有 3 秒會爆炸,並以 3 - 1 ,也就是 2, 帶入自己這個函數。
- 當 n 是 2, 它大於 0, 因為符合 else 的條件,所以印出還有 2 秒會爆炸,並以 2 - 1 ,也就是 1, 帶入自己這個函數。
- 當 n 是 1, 它大於 0, 因為符合 else 的條件,所以印出還有 1 秒會爆炸,並以 1 - 1 ,也就是 0, 帶入自己這個函數。
- 當 n 是 0, 它小於等於 0, 因為符合 if 的條件,所以印出爆炸並結束函數。
執行結果像這樣:
還有 3 秒會爆炸
還有 2 秒會爆炸
還有 1 秒會爆炸
爆炸
這就是 遞迴。再舉一個例子,我們寫一個印 n 次變數 s 的遞迴函數:
def printN(s, n):
if n <= 0:
return
print(s)
printN(s, n - 1)
當 n 小於或等於 0 時,使用 return 結束函數,並返回 __main__
,否則就印出變數 s 的值,並再次以 n - 1 為參數呼叫自己這個函數,流程與上個例子類似。
雖然這個例子使用迴圈會更容易撰寫,但後面我們會發現,有些情況使用遞迴比較好寫,因此先把重點放在學習遞迴上面。
5-9 無限遞迴
如果一個遞迴函數最後一直沒有回到 __main__
或結束函數的基本敘述,就會讓函數一直呼叫自己。
這當然不是個好主意,我們稱這種情況為 無限遞迴。
def recurse():
print('一直印,不要停')
recurse()
它真的會一直印,不要停。因為我們沒有給它停止的條件。或者說,我們沒有讓結束的條件發生。
不過幸好在 python 中不會真正無限地執行遞迴函數,而是當遞迴的深度到達極限時,停止程式並報錯。
類似底下的狀況:
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded
它可能會執行一段時間才終止並報錯,例如已經有 1000 個遞迴函數框架在系統中,使用遞迴時切記不要發生這種情況。
5-10 鍵盤輸入
到目前為止,我們的程式還沒辦法接受使用者輸入的資訊,都是我們以自己擬定的值作為參數來運作。
在 python 中提供 input() 函數來等待使用者輸入,按下 enter 結束輸入,並將使用者輸入的資訊以字串型態回傳。
>>> text = input()
打字測試
>>> text
打字測試
讓我們改善上面的範例,在畫面上直接提示使用者應當輸入什麼資料是個好做法,input() 接受一個字串參數作為提示語。
>>> name = input('請輸入你的大名:\n')
請輸入你的大名:
python
>>> name
python
\n
是一個換行符號,這也就是為什麼我們會在提示字串的下一行進行輸入的原因。
如果你希望收到的值是整數,那你可以轉換資料型態:
>>> num = input('請輸入整數:')
請輸入整數: 100
>>> num
'100' # 有引號是個字串
>>> int(num)
100 # 轉成整數
上面使用了型別轉換,但如果使用者輸入的是非數字的字串,則會發生錯誤:
>>> num = input('請輸入整數:')
請輸入整數: abc
>>> int(num)
ValueError: invalid literal for int() with base 10
後續我們將介紹如何處理這樣的錯誤狀況。
總之,一定要記住,當使用者輸入後回傳的值,一定是一個字串。
除錯
當程式一次輸出很多錯誤訊息時,我們可能會不知如何是好,把握兩個重點:是什麼樣的錯誤?發生在什麼地方?也就是英文的 what 與 where.
通常語法錯誤比較容易發現,遇到縮排問題修改起來會比較麻煩,但對使用導讀軟體的視障者,可能是個例外。
這部分我們在前面的章節有討論過,看一下這個例子:
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent
錯誤指向 y ,但實際上是前面的 tab 符號縮排導致的,而且行號顯示為第一行。
通常 python 提示我們的錯誤之處是參考用的,所以在除錯時,我們應該針對系統給的錯誤點,上下擴大一些範圍來查找錯誤的根源會比較好。
另外,監視變數或函數的回傳值也有助於查找錯誤的根源。
下面是一個可能發生錯誤的程式,讓我們測試看看並修改它。
def func(n):
if n > 0:
print(n, '大於 0')
num = input('請輸入一個數字 ')
result = num - 10
func(result)
動動腦
1.如果沒有取餘數運算子的話,那我們有什麼辦法取得餘數呢?
def getMod(x, y):
m = x - (y * (x // y))
print(m)
getMod(12, 7)
想法來自於: 被除數等於除數乘以商加餘數
2.寫一支程式,可以顯示現在的時間,並根據現在的時間打招呼,中午十二點前顯示早安,中午十二點後顯示午安。
from datetime import datetime as dt
nt = dt.now()
print('現在的時間是:', nt)
if nt.hour < 12:
print('早安')
else:
print('午安')
3.短判斷
一般的 if-else 判斷在上述章節提過,事實上,各種語言都有所謂短判斷的寫法。
短判斷又稱三元運算,就是由三個運算元構成的判斷式。
短判斷可讓程式更簡潔,在使用上跟列表表達式一樣,但條件複雜時,還是使用一般的寫法會比較好維護與寫註解。
例如,不要使用嵌套,也就是巢狀的短判斷,因為這樣就不短了。
以下是短判斷的範例:
s = '大於' if a > b else '不大於'
if 左邊是條件為真所執行的敘述,else 右邊是條件為假執行的敘述,而 if 和 else 之間則是判斷條件。
當 a 大於 b 時, s 就是 '大於' 字串,否則 s 為 '不大於' 字串。
有些程式語言的短判斷是用符號,在 python 還是使用原來的 if-else ,這樣似乎比較一致且容易理解。
短判斷也可以直接用在 print 裡面,以下是個範例:
print('猜對了' if guess == puzzle else '你輸了')
也可以搭配 format() 使用:
num = int(input('輸入整數: '))
print('{0} 為 {1}'.format(num, '奇數' if num % 2 else '偶數'))
有時候的確還滿方便的,但記得不要試著去把複雜的 if 巢狀判斷式改成短判斷,
這樣只會增加維護的難度,對於一起寫程式的夥伴來說,不是件好事。
你也不應該因為可以把複雜的判斷式改成短判斷而感到高興,因為過一段時間後,自己可能也會看不懂。
總之,一件事情沒有絕對,但至少出現這樣的語法時,我們能讀懂它。
練習
第1題 檔名 5-1.py
撰寫一個名為 guess 的函數,接受兩個參數,分別是 ans 與 num。
- 當 num 大於 ans 時,顯示「猜小一點」
- 當 num 小於 ans 時,顯示「猜大一點」
- 當兩者一樣時,顯示「猜對了」
第2題 檔名 5-2.py
任兩邊長度的和必須大於第三邊,且任兩邊的差必須小於第三邊,滿足以上兩個條件的三邊才能組成三角形;
(1)寫一個名為 isTriangle 的函數,接受三個參數作為三邊長,功能是如果這三個參數可以構成三角形,則印出 ok,否則印出 no
(2)寫一個程式讓使用者輸入三個數字,並轉成整數,再利用 is_triangle 函數確認是否可以構成三角形。
第3題 檔名 5-3.py
寫一個可以找錢的函數。
(1) 讓使用者輸入一個數字代表多少錢。
(2) 印出你目前有多少錢。
(3) 接著印出可以換成多少零錢(最少零錢方案),還剩多少零錢。
(4) 換錢的功能需要寫成一個函數。
(5) 假設零錢沒有一個二十塊的。
執行結果範例:
請輸入多少錢: 127
你目前有 127 元
可以換成:
50 元 2 個
10 元 2 個
5 元 1 個
1 元 2 個
第4題 檔名 5-4.py
寫一個函數,證明 n 個數的立方和等於 n 個數的和的平方。
執行結果參考:
請輸入一個大於0的正整數: 3
1~3 + 2~3 + 3~3 = 36
(1 + 2 + 3)~2 = 36
請輸入一個大於0的正整數: 10
1~3 + 2~3 + 3~3 + 4~3 + 5~3 + 6~3 + 7~3 + 8~3 + 9~3 + 10~3 = 3025
(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10)~2 = 3025
這裡我們先用 ~
代表次方的符號。
提示: for 迴圈的 range() 函數可以接受兩個參數,第一個參數是迴圈的起始點,第二個參數是結束點加一。
>>> for i in range(3, 6):
... print(i)
...
3
4
5
第5題 檔名 5-5.py
改寫上面的動動腦第2題,我們希望讓打招呼程式更靈活一點。
- 凌晨3點到中午十二點前說早安
- 中午十二點到晚上六點前說午安
- 晚上六點以後到凌晨三點說晚安
第6題 檔名 5-6.py
for 迴圈與 range() 練習,列出每小題符合條件的 for 迴圈並印出數值。
- 從 100 到 1 之間的所有奇數。
- 從 10 到 100 之間 5 的倍數。
- 從 3 到 10 之間的每個數字,規定 range() 內一定要用三個參數。
第7題 檔名 5-7.py
寫出符合下列所有條件的虛擬碼程式。
- 下雨就帶傘出門
- 晴天就戴太陽眼鏡出門
- 陰天就背包包出門
- 其他狀況就在家睡覺不出門
參考資料
影片
最後更新:2021-10-08 22:00:28
From: 111.249.165.250
By: 特種兵