# Python Programming - Lecture 03 流程控制
###### tags: `python-programming`
人生當中充滿了決策,當中不乏許多困難的決策,像是今天晚餐要吃什麼,明天早上要不要早起之類的。
所謂的「決策」,就是根據給定的條件,判斷未來是否要採取某些行動,或是要採取哪些行動等等。以吃晚餐的例子來說,你可能會根據今天帳戶的餘額來決定晚上要吃好一點還是吃省一點。在此處,「今天帳戶的餘額」就是給定的條件、「吃好一點」或是「吃省一點」就是可能採取的行動。
在電腦程式當中,透過決策來影響程式執行步驟的行為,就叫作「流程控制」。在本章當中,我們將要來學習和流程控制有關的基本觀念與程式語法。
## 3.1 條件判斷
決策的第一個步驟,就是進行條件判斷。通常我們要做決策時,手頭上都會有一些資訊,會影響我們未來的行動。如果手頭上的資訊**滿足某些條件時**,我們就會決定要做某些動作。值得注意的是,我們手上的資訊**要嘛就是會滿足這些條件,要嘛就是不滿足這些條件**,不會有同時滿足又不滿足的狀況。因此,**<span style="color: red">條件判斷的結果,必定要是一個布林值,用來代表條件是否滿足</span>**。只要記住這個最基本的原則,你大概就可以掌握未來所有流程控制的條件表達式應該如何撰寫。
在電腦程式當中,由於每一個運算步驟都必須非常明確,不可以有模糊的空間,因此我們用來做條件判斷的資訊通常會是可量化的,或是有明確定義的東西。舉例來說:「如果這個妹仔**長得夠正**的話...」這樣的判斷對電腦來說是沒有意義的,因為到底怎麼樣才叫做「夠正」呢?這個判斷條件並沒有一個客觀的依據;但如果是「如果這個妹仔的 **IG 追蹤人數大於 1000 人**的話...」這樣的判斷就有意義了,因為 IG 的追蹤人數是一個整數,而整數的大於判斷是所有人都一致認同有固定作法的運算。那麼除了整數的大於判斷,還有哪些運算也是像這樣具有明確的非黑即白的結果呢?
### 3.1.1 條件表達式
在 Python 中,運算結果為布林值,可以用來當作流程控制用的條件判斷的運算式稱作**條件表達式 (Conditional expression)** 。我們在講中文的時候常常會說「如果 OOOO,那我就要 XXXX」這樣的句子,在這個句子當中,「OOOO」的部份就是我們的條件判斷。事實上,由於 Python 是一個很接近英文口語的語言,所以上面這句話幾乎可以直接被改寫成 Python 的程式碼:
```python
>>> if [conditional expression]:
... # do something
```
這就是我們稍後會在 3.2 節當中學習到的 `if` 語句。在 `if` 這個關鍵字的後面所放的句子,就是條件表達式。
就如同我們在前一章中學到的,**比較運算子**的運算結果必定為布林值,因此比較運算式就可以用來當作條件表達式。除了比較運算之外, **`in` 運算**和 **`is` 運算**同樣也是計算結果為布林值的一種運算,因此也經常用在條件表達式上。
比較運算包含了比大小的運算以及相等判斷的運算,是最常被用到的條件判斷之一。諸如「若餘額等於大於 0 時」、「若身高小於 140 公分時」、「若輸入字串和資料庫內的使用者名稱相同時」等等,這些都是使用到比較運算子的條件表達式
```python
>>> balance = 1200
>>> balance >= 0
True
>>> height = 155
>>> height < 140
False
>>> input_username = 'kinoe'
>>> input_username == 'kinoe'
True
```
`in` 運算可以用來檢驗字串是否有包含某個子字串。 `in` 運算子是一個二元運算子,第一個運算元是想要尋找的子字串,第二個運算元則是用來檢驗的目標。當我們寫 `str1 in str2` 時,意思就是說我們想要檢查 `str1` 這個字串有沒有存在於 `str2` 這個字串裏面。
```python
>>> "lie" in "believe"
True
>>> "fun" in "coding"
False
```
`in` 運算也可以用來檢查某個資料結構有沒有包含某個元素,這個用法我們將會在第五、第六章內探討。
`is` 運算用來檢查兩個變數是否擁有一樣的記憶體位址。這個運算子同樣要等到我們學習到各類的資料結構後才會用到。
上述這些運算子,都是 Python 內建的會算出布林值的運算子。但就像我們一開始提到的,只要運算結果是一個布林值,其實都是可以被當作條件表達式來使用的。例如:在許多函式庫當中,都會有專門用來測試某些條件的函式存在,這些函式可能檢查了一連串的條件,最後回傳一個布林值。像這樣的檢查用函式,也相當常被當作條件表達式來使用。
:::info
:memo: **非布林值作為條件表達式**
有時候,你可能會看到有人寫出這樣的程式:
```python
>>> x = 10
>>> if x:
... print(x)
...
10
```
什麼?!為什麼 `if` 後面放了一個整數,而不是一個會產生布林值的東西?
我們在第一章當中有學到,所有基本的資料型別都是可以轉換成布林型別的,我們可以透過 `bool()` 函式來觀察轉換後的結果。當一個不是布林值的東西被當作條件表達式來使用的時候, Python 會自己先偷偷呼叫這個 `bool()` 函式,得到該資料的布林版本結果之後,再使用這個結果去進行條件判斷。若該資料是 Falsy value,那麼條件判斷的結果就是 `False` ,否則就是 `True`。因此,上述的程式其實跟
```python
>>> x = 10
>>> if x != 0:
... print(x)
```
是一樣的意思。
你也可以試試看使用其他型別的 Falsy value 來撰寫條件表達式,看看會有什麼結果~
:::
### 3.1.2 邏輯運算子
現在我們已經學會了基本的條件判斷,但是生活中的許多問題,往往不是只靠單一的條件就能下決策的。像是晚餐要吃什麼這個問題,除了考慮帳戶的餘額之外,你通常應該也會考慮自己當時所在的地理位置、當天想吃的料理口味、最近的運動量、最近的食量等等其他因素。此時你的決策過程可能就會是「如果我戶頭存款大於 1000 塊**而且**我所在位置 500 公尺內有拉麵**而且**我今天想吃拉麵**而且**我過去一週內跑步有跑超過 5 公里**而且**我過去三天內吃東西沒有超過 5000 大卡的話,我就要吃拉麵」。

像這樣有許多條件需要共同考慮時,我們就會用到**邏輯運算子 (Logical operator)**。 Python 中的邏輯運算子有**且 (`and`)** 、**或 (`or`)** 和**非 (`not`)** 三種,可以用來串接多個條件表達式,進而表達複雜的條件判斷。而運用邏輯運算子串接而成的句子,就叫作**複合表達式 (Compound expression)**。複合表達式的語意跟我們的口語是一樣的,例如:當你說「如果今天是禮拜天**而且**今天晚上九點半以後我沒事的話,我就要來上 Python 的課程」,那麼你就只會在「今天是禮拜天」以及「今天晚上九點半之後我沒事」這兩句話都成立的時候,才會參加 Python 的課程。在這句話當中,「今天是禮拜天」是第一個簡單的條件表達式,「今天晚上九點半之後我沒事」是第二個簡單的條件表達式,「而且」就是你使用到的邏輯運算子,而整個條件判斷成立與否(最後會不會上課),是由兩個條件表達式一起決定的。
使用不同的邏輯運算子可以用來表達不同的決策邏輯,各個邏輯運算子的功能描述如下:
* **且 (`and`)**:用來判斷兩個條件表達式是不是同時都成立(回傳 `True`)。當兩個條件表達式都成立時,這一整句複合表達式就成立,否則就不成立。我們可以利用[**真值表**](https://zh.wikipedia.org/zh-tw/%E7%9C%9F%E5%80%BC%E8%A1%A8)來描述邏輯運算子的判斷規則,且運算子的真值表為:
|`exp1`|`exp2`|`exp1 and exp2`|
|-|-|-|
|`False`|`False`|`False`|
|`False`|`True`|`False`|
|`True`|`False`|`False`|
|**`True`**|**`True`**|**`True`**|
我們可以用「判斷 `x` 是不是正偶數」這個例子來觀察 `and` 運算子的判斷規則。在這個任務中,我們想要知道 `x` 是否滿足「`x` 大於 0」而且「`x` 除以 2 的餘數是 0」這兩個條件,此時就會使用到 `and` 運算子:
```python
>>> x = 2
>>> x > 0 and x % 2 == 0 # Is x a positive even number?
True
>>> x = 1 # Positive, but not even number
>>> x > 0 and x % 2 == 0
False
>>> x = -2 # Even, but not positive number
>>> x > 0 and x % 2 == 0
False
>>> x = -1 # Neither positive nor even number
>>> x > 0 and x % 2 == 0
False
```
* **或 (`or`)**:用來判斷兩個條件表達式是不是**至少有一個**成立。當兩個條件表達式有其中一個成立,**抑或兩者都成立時**,這一整句複合表達式就成立,否則就不成立。或運算子的真值表為:
|`exp1`|`exp2`|`exp1 and exp2`|
|-|-|-|
|`False`|`False`|`False`|
|**`False`**|**`True`**|**`True`**|
|**`True`**|**`False`**|**`True`**|
|**`True`**|**`True`**|**`True`**|
我們可以用「判斷 `c` 這個字母是不是母音」這個例子來觀察 `or` 運算子的判斷規則。在這個任務中,我們想要知道 `c` 是否滿足「`c` 有出現在 `"AEIOU"` 當中」或是「`c` 有出現在 `"aeiou"` 當中」其中之一,此時就會使用到 `or` 運算子:
```python
>>> c = 'a'
>>> c in 'AEIOU' or c in 'aeiou'
True
>>> c = 'E'
>>> c in 'AEIOU' or c in 'aeiou'
True
>>> c = 'b'
>>> c in 'AEIOU' or c in 'aeiou'
False
```
* **非 (`not`)**:用來判斷一個條件表達式**是否不成立**。當該條件表達式不成立時,整句話成立,否則就不成立。否運算子的真值表為:
|`exp`|`not exp`|
|-|-|
|`True`|`False`|
|**`False`**|**`True`**|
我們可以使用以下程式範例來觀察 `not` 運算子的判斷規則:
```python
>>> x = 0
>>> x == 0
True
>>> not x == 0
False
>>>
>>> 'fun' in 'coding'
False
>>> not 'fun' in 'coding' # True... :(
True
```
<!-- :::info
:memo: **邏輯運算的短路求值**
* [短路求值](https://zh.wikipedia.org/wiki/%E7%9F%AD%E8%B7%AF%E6%B1%82%E5%80%BC)
:::
-->
邏輯運算子是可以連用的,例如「判斷 `x` 的個位數字是不是 5」可以想成是判斷「`x` 是 5 的倍數」而且「`x` 不是 2 的倍數」,此時就會連用 `and` 運算子和 `not` 運算子:
```python
>>> x = 365
>>> x % 5 == 0 and not x % 2 == 0
True
>>> x = 10
>>> x % 5 == 0 and not x % 2 == 0
False
```
當我們連用邏輯運算子時,這三個運算子的運算優先度是 **`not` > `and` > `or`**。也就是說,如果沒有寫任何的小括號的話,會優先計算 `not` 的部份,再算 `and` 的部份,最後才算 `or` 的部份。舉例來說:
```python
# Example 1
exp1 or not exp2 and exp3
= (exp1 or ((not exp2) and exp3))
# Example 2
exp1 and exp2 or exp3 and exp4
= ((exp1 and exp2) or (exp3 and exp4))
```
我個人推薦把 `not` 想成取負號、把 `and` 想成乘法、把 `or` 想成加法,會比較好記憶這些運算子的優先度。
透過連用邏輯運算子,我們可以表達出更複雜的判斷決策。有時候同一種決策方式有多種邏輯表達法,這樣的狀況我們稱這些不同的表達法之間**邏輯等價**。關於如何撰寫邏輯等價的句子,可以參見[附錄](https://hackmd.io/@kaeteyaruyo/python-programming-appendix)。
## 3.2 流程控制
決策的第二個步驟,就是根據條件判斷的結果採取行動。畢竟我們應該不會像這樣講話:
:::success
:rage: 「如果我今年之內瘦到 50 公斤的話!」
:face_with_one_eyebrow_raised: 「...要幹嘛?」
:rage: 「!!!」
:face_with_one_eyebrow_raised: 「???」
~~(好像不講也沒差拉反正是恆不成立的條件()~~
:::
當我們的程式需要根據條件判斷的結果採取不同行動時,我們可以使用 **`if` 語句 (`if` statement)**,以及它的幾種變體。 `if` 語句是構成複雜程式的最基本要素,透過 `if` 語句,我們一定程度上將人類的智慧(思考以及決策的能力)賦予了程式。稱使用 `if` 語句撰寫出來的程式是最基本的人工智慧,從定義上來說是一點都不為過的。

> Fig 3.1 「好了大夥們,讓我們來看看人工智慧的真面目吧!」
以下我們將分別介紹 `if` 語句與其變體的運作原則。
### 3.2.1 `if` 語句
`if` 語句是最基本的流程控制語句,用來表達「若某條件成立,則進行某動作」的語意。 `if` 語句的語法如下
```python
>>> if conditional_expression:
... statement
```
這個語句的組成內容有:
* 關鍵字 `if`
* 條件表達式 `conditional_expression`
* 冒號 `:`
* <span style="color: red">**縮排**</span>
* 一到多句的運算動作 `statement`
這五個元素缺一不可,只要少打任何一個東西,都會被判定為語法錯誤。可以注意到 `statement` 前,直譯器的提示字從 `>>>` 變成了 `...` ,這表示 `if` 語句不是只有第一行那些東西,而是至少要再打一些東西才可以完成的語句。往後我們會學到,任何帶有冒號 `:` 的語句,都是像這樣至少要有兩行才會完成的語句。
`if` 語句的運作規則為:當 `conditional_expression` 的判斷結果為 `True` 時, `statement` 就會被執行;若為 `False` ,就不會被執行。**注意 `statement` 前必須有縮排,才會被視為 `if` 語句的動作部份。** 被縮排的部份則被稱為**程式碼區塊 (block)** ,我們會在 3.3 節詳細說明程式碼區塊的性質。
`if` 用來表達某些行為只在特定條件下才會觸發,例如:當我們要登入網站的時候,必須要進行密碼的比對,才能確認使用者是否為本人。**如果**使用者輸入的密碼與資料庫內記載的密碼相符的話,**則**讓該使用者登入(只有在密碼正確的時候,使用者才可以登入)。這個邏輯就可以使用 `if` 語句來表達:
```python
>>> password = 'p4ssw0rd'
>>> input_password = 'p4ssw0rd'
>>> if input_password == password:
... print('User logged in')
...
User logged in
>>> input_password = 'password'
>>> if input_password == password:
... print('User logged in')
...
>>>
```
### 3.2.2 `if-else` 語句
`if-else` 語句用來表達「若某條件成立,則進行某動作,**否則**進行另一個動作」的語意。 `if-else` 語句的語法如下
```python
>>> if conditional_expression:
... statement1
... else:
... statement2
```
這個語句的組成內容有:
* 關鍵字 `if`
* 條件表達式 `conditional_expression`
* 冒號 `:`
* 縮排
* 一到多句的運算動作 `statement1`
* 關鍵字 `else`
* 冒號
* 縮排
* 一到多句的運算動作 `statement2`
`if-else` 語句的運作規則為:當 `conditional_expression` 的判斷結果為 `True` 時, `statement1` 會被執行;若為 `False` ,則 `statement2` 會被執行。
有時我們在給定條件不成立的情況下,並不是希望什麼都不做,而是可以做一些替代的動作,此時就會用到 `if-else` 語句。例如:當你去提款機領錢的時候,如果你提領的金額比帳戶餘額還要少或相等的話,那麼就必須要讓你提領,並且要從帳戶內扣除對應的金額;但如果要求要提出比帳戶餘額還要多的金額的話,提款機應該要提示你無法提款。這樣的行為就可以用 `if-else` 語句來描述:
```python
>>> balance = 500
>>> withdrawal = 250
>>> if withdrawal <= balance:
... balance -= withdrawal
... print("成功提款,帳戶餘額為", balance)
... else:
... print("餘額不足")
...
成功提款,帳戶餘額為 250
>>> withdrawal = 1000
>>> if withdrawal <= balance:
... balance -= withdrawal
... print("成功提款,帳戶餘額為", balance)
... else:
... print("餘額不足")
...
餘額不足
```
### 3.2.3 `if-elif` 語句
`if-elif` 語句用來表達「若某條件成立,則進行某動作,**否則若另一個條件成立,則**進行另一個動作」的語意。 `if-else` 語句的語法如下
```python
>>> if conditional_expression1:
... statement1
... elif conditional_expression2:
... statement2
```
這個語句的組成內容有:
* 關鍵字 `if`
* 條件表達式 `conditional_expression1`
* 冒號 `:`
* 縮排
* 一到多句的運算動作 `statement1`
* 關鍵字 `elif`
* 條件表達式 `conditional_expression2`
* 冒號 `:`
* 縮排
* 一到多句的運算動作 `statement2`
`if-elif` 語句的運作規則為:當 `conditional_expression1` 的判斷結果為 `True` 時, `statement1` 會被執行;若為 `False` ,**則會再進一步判斷 `conditional_expression2` 是否成立**,若 `conditional_expression2` 判斷結果為 `True` ,則 `statement2` 會被執行,否則就不執行。
有時我們要處理的問題有可能會產生多種(大於兩種)不同的狀態,當我們希望在每一個狀態下都採取不同的行動時,就會用到 `if-elif` 語句。舉例來說:在玩終極密碼的時候,玩家所猜的數字有可能比正確答案還要大、還要小,或是一樣大。在這三種不同的情況下,我們所應採取的行動是不一樣的。如果玩家猜的數字比較大,那我們必須提示「小一點」;如果玩家猜的數字比較小,那我們必須提示「大一點」;如果玩家猜的數字和答案一樣大,那我們必須提示「猜對了」。這樣的邏輯就可以使用 `if-elif` 語句來描述:
```python
>>> answer = 12
>>> query = 50
>>> if query > answer:
... print("小一點")
... elif query < answer:
... print("大一點")
... elif query == answer:
... print("猜中了")
...
小一點
>>> query = 9
>>> if query > answer:
... print("小一點")
... elif query < answer:
... print("大一點")
... elif query == answer:
... print("猜中了")
...
大一點
>>> query = 12
>>> if query > answer:
... print("小一點")
... elif query < answer:
... print("大一點")
... elif query == answer:
... print("猜中了")
...
猜中了
```
就像上方的程式範例這樣, `if-elif` 語句是可以連續使用的,可以有多個 `elif` 接在 `if` 後面。條件判斷的規則是從上往下判斷,也就是當第一個條件不成立時,判斷第二個條件;若第二個條件不成立時,判斷第三個條件...,以此類推。
在 `if-elif` 語句的最後,若想要額外處理「上述所有條件通通都不成立的話」的情況,也可以接上一個 `else` 作為結尾。因此,上述的例子其實可將最後的 `elif query == answer:` 直接改為 `else:` ,程式運作的邏輯也是不變的。因為根據三一律,兩個數字之間的關係一定只會是大於、小於或等於其中之一,若既不是大於、也不是小於,那麼必定就只會是等於的關係了。
<!-- ### 3.2.4 Switch cases -->
## 3.3 程式碼區塊
在 `if` 系列語句當中,我們首次遇到了**程式碼區塊 (block)** 的概念。所謂的程式碼區塊,是指一些特殊結構的程式碼**有效的範圍**,有時也會說是該結構的**主體 (body)**。例如:在 `if` 語句當中,程式碼區塊定義了「如果條件成立的話必須執行」的程式碼範圍。除了 `if` 系列語句之外,往後我們還會學到迴圈、函式、 `with` 語句、例外處理、物件定義等等,在這些特殊的結構當中,程式碼區塊都會被用來限定這些特殊結構的功能可以影響到的範圍。
在其他許多程式語言當中,程式碼區塊是使用大括號 `{ }` 來定義的,只要被大括號括起來,就自成一個程式碼區塊。此外,這些語言往往也用分號 `;` 來定義語句,在一個語句的尾巴打一個 `;` ,就可以知道語句已經結束。但是這樣的設計有時會導致程式碼的不美觀,因為忽略程式碼的縮排、換行等排版細節依然可以正常的撰寫程式碼,就會使得一些人寫出排版混亂、難以閱讀的程式碼。在 Python 中,為了避免上述的情形發生, Python 被設計成**使用縮排來定義程式碼區塊、使用換行來定義語句**。這確保了 Python 的程式設計師們如果想要寫出特定的邏輯,所有人寫出來的程式碼長相會長得差不多,在閱讀與溝通上就會比較流暢。這是 Python 和其他程式語言之間一個顯著的差異。
在 Python 中,我們可以使用空白鍵或是 tab 鍵(在鍵盤的左上角,Esc 下面兩個)來進行縮排。雖然 Python 沒有明定縮排時要用幾個空白鍵或是幾個 tab 鍵,理論上你想要用幾個都可以,但是在 Python 社群中習慣上是以 **4 個空白鍵**或是 **1 個 tab 鍵**來進行縮排的。在一段連續的程式碼中,若每一行前面都加上縮排,那麼這一段程式碼就會變成一個程式碼區塊。舉例來說:
<!-- TODO: 這裡的例子不好,換一個有實際意義的 -->
```python=
>>> a = 5
>>> if a > 0:
... a += 3
... a *= 2
... print(a)
...
16
```
上方的程式範例中,第 3 行到第 5 行這段連續的程式碼前都有縮排,因此第 2 行到第 4 行是一個程式碼區塊,他們都會在 `a > 0` 這個條件成立的情況下被執行。
若一個程式碼區塊當中還有另一個程式碼區塊,那我們就會說這樣的結構是**巢狀的 (nested)** 程式碼區塊。由於程式碼區塊是用縮排在定義的,所以如果想要在程式碼區塊當中定義另一個程式碼區塊,那就再往裏面再縮一次排就可以了。例如:
<!-- TODO: 這裡的例子不好,換一個有實際意義的 -->
```python=
>>> a = 5
>>> if a > 0:
... a = -a
... if a % 2 == 0:
... a /= 2
... else:
... a += 1
... print(a)
...
-4
```
在上方的程式碼範例中,第 3 行到第 8 行都有一個縮排,因此第 3 行到第 8 行是一個程式碼區塊,但是第 5 行、第 7 行**在已經有第一個縮排的情況下,還有第二個縮排**,因此他們是在 3 - 8 這個程式碼區塊當中的另一層程式碼區塊。第 4 行到第 7 行的 `if-else` 語句可以影響到的程式碼範圍就只有第 5 行和第 7 行而已,無論第四行的 `a % 2 == 0` 判斷結果為何,第 8 行的 `print(a)` 都一定會執行,因為第 8 行和第 5 行、第 7 行並不屬於相同層級的程式碼區塊。
當流程控制的邏輯相當複雜的時候,就會出現條件判斷和迴圈互相層層套疊的狀況。程式碼區塊便是用來區分這些巢狀的流程控制語句自身可以影響到的範圍。
## 3.4 腳本檔
隨著我們學習的語法愈來愈多,程式的規模就會漸漸長大。當你的 `if` 語句主體超過 10 行的時候,你大概就不會想要在直譯器當中編輯程式了。因為如果你在第九行打錯一個字的話,就要從第一行再重頭打一次了。事實上, Python 程式設計師們如果要撰寫真正可以使用的程式,也不會將程式碼寫在直譯器當中,因為程式碼的最大用意就是自動化,如果不能被重複利用,不就沒有意義了嗎?因此,當我們要開始撰寫更大規模且有用的程式時,我們就必須學會如何撰寫 **Python 腳本檔 (script)**。
由於 Python 是一個腳本語言(也就是直譯式語言),因此我們會稱寫有 Python 程式碼的檔案叫作 Python 的腳本檔,有時也會直接用副檔名來稱乎說是 `.py` 檔。撰寫腳本檔的好處就是:你可以將很大量、很複雜的程式邏輯事先準備好,並且在想要使用這個很複雜的邏輯的時候,用簡單的一句指令就能執行。
舉個例子:每一次出去玩時我都會拍一堆照片(可能有上千張),照相機預設的檔名用的是流水號,但我希望我可以從檔名就知道我是什麼時候拍的照片,因此我想要用這張照片被儲存時的時間戳記當作檔名。如果不使用任何程式來幫忙的話,我就必須要先右鍵打開檔案屬性來查看照片的拍攝日期,然後再點選檔名的欄位,輸入照片的拍攝日期,然後再儲存。如此重複一千多次,每一次出去玩回來都要這樣做。要是我真的必須這麼做,我大概會直接放棄整理照片。但是如果可以藉由 Python 的程式來幫忙的話,這個動作就會變得相對簡單且快速。
問題是,這個重新命名檔案的邏輯好像不是一兩句話就可以說完的?可能至少要寫個十幾行 Python 程式碼才有辦法完成。如果我只有 Python 直譯器可以用,難道每次整理照片都要再寫一樣的程式碼嗎? Again,要是我真的必須這麼做,我大概還是不會整理照片。如果可以把處理檔案重新命名的程式邏輯寫在一個檔案裏面,而每次我要重新命名新的相片們時,我都只要跑一條指令然後輸入相片所在的資料夾名字,他就會幫我把所有的照片都重新命名好,那就太棒了。
腳本檔的功能就是用來滿足上述的這類需求,由於類似的需求每分每秒都在發生,因此學習如何撰寫和執行腳本檔是不可避免的。那麼要怎麼撰寫這樣的腳本檔呢?我們又要怎麼讓自己或別人使用這些腳本檔呢?
### 3.4.1 撰寫與執行腳本檔
撰寫 Python 腳本檔非常簡單。首先,在你的電腦中新增一個檔案,並取名叫作 `script.py` (`script` 的部份可以代換成任何的檔名,取決於這份程式碼的功能是什麼),這樣你就擁有一個 Python 腳本檔了。以下以 Linux 系統做為範例,建立一個新的 Python 腳本檔:
```shell
$ touch script.py
$ ls
script.py
$
```
當然,現在這個腳本檔裏面還什麼都沒有,所以是沒有任何功能的。下一步就是要往這個腳本檔當中加入一些程式碼,讓他變得有用。你可以用任何編輯器開啟這個腳本檔,然後在裏面寫入一些合法的 Python 程式碼,例如:
```python
# script.py
name = 'kinoe'
print('Hello, ', name)
```
這樣這個腳本檔就有在 CLI 中印出文字的功能了!但是要怎麼執行這個腳本檔呢?只是用滑鼠點兩下這個檔案,看起來只會打開文字編輯器而已。要讓電腦知道我們現在是想要執行腳本,而不是編輯腳本,就必須要對電腦下達正確的指令。在第零章當中,我們有提到 `python3` 這個直譯器軟體,除了直接執行以開啟直譯器之外,也可以餵入一個腳本檔來執行腳本檔。因此我們必須在終端機中輸入 `python3 script.py` 才能夠執行這份腳本檔:
```shell
$ cat script.py
# script.py
name = 'kinoe'
print('Hello,', name)
$ python3 script.py
Hello, kinoe
```
現在你知道怎麼叫電腦執行一個腳本了。恭喜你,你已經俱備了可以撰寫大型程式的資格了!(響起獲得成就的效果音)
### 3.4.2 基本輸入輸出
現在我們已經有一個腳本檔了,但是好像每一次執行它,輸出出來的結果都一樣?一個程式必須要可以根據不同使用者的需求做出不同的回應,才能算是有用的程式。但現在如果我們要改變這個程式的行為,還要特地打開檔案、修改程式碼,然後再存檔才能讓他做出不一樣的事。那麼不會寫程式的人,豈不是不能使用這個工具了嗎?
為了讓不會寫程式的使用者也可以使用我們所撰寫的工具,我們的程式必須要有能力可以應對使用者的需求才行。具體來說,我們的程式必須要有:
* 得知使用者的需求的能力,也就是**輸入 (input)** 的能力
* 將運算結果顯示給使用者看的能力,也就是**輸出 (output)** 的能力
因此,我們就會需要用到 `input()` 和 `print()` 這兩個內建的輸入輸出函式。
到目前為止,我們所撰寫的腳本檔都還只是 CLI 程式,也就是只能透過鍵盤輸入文字來互動,沒有圖形化介面的程式。 `input()` 函式便是 Python 當中用來獲取 CLI 的鍵盤輸入用的一個內建函式。當 `input()` 函式被呼叫時,程式會暫停執行,等待使用者輸入文字。 `input()` 函式會紀錄使用者所輸入的文字,直到使用者輸入了一個換行(newline,透過 Enter 鍵來輸入), `input()` 函式才會停止接收輸入,並把到換行前一個字為止的所有文字放進一個字串裡回傳出來:
```python=
>>> input()
Hello world!
'Hello world!'
```
上方程式範例中的第一行是在呼叫 `input()` 函式;第二行是使用者所輸入的文字,可以觀察到中間的空白鍵也會被紀錄下來,只有換行才可以讓 `input()` 停止紀錄文字;第三行則是在我輸入完文字之後按了一下 Enter 鍵, `input()` 因此停止紀錄文字並把換前一個字為止的字串回傳出來。
你可以在 `input()` 函式的小括號裏面放一個字串,這個字串會被作為使用者輸入前的提示文字顯示在 CLI 中,這會讓使用者更清楚自己現在要做什麼:
```python
>>> input('Your username: ')
Your username: kinoe
'kinoe'
```
要注意的是, **`input()` 把所有的使用者輸入都視為文字,就算你打的是數字也一樣**。因此如果你想要把使用者輸入的東西當作數字處理的話,你必須要自己呼叫對應的轉換函式:
```python
>>> input('How old are you: ')
How old are you: 24
'24'
>>> int(input('How old are you: '))
How old are you: 24
24
```
現在你可以獲取使用者輸入了。根據使用者輸入不同的東西,你的程式會計算出不同的結果來。如果程式計算完了卻什麼都沒有輸出,使用者大概會一頭霧水。因此,把計算結果顯示給使用者看也是很重要的。在 Python 中,我們可以使用 `print()` 函式來將文字顯示在 CLI 上面。在先前的一些程式範例中,其實我們已經有偷偷用到了 `print()` 函式。最簡單的用法就是在 `print()` 函式的小括號裏面放進一個字串,該字串就會被顯示在 CLI 上:
```python
>>> print('Hello')
Hello
```
`print()` 函式也可以接受兩個以上的字串,如果想要在一行之內輸出多個字串,可以用逗號 `,` 分隔小括號內的多個字串,這些字串就會一起被打印出來,但每一個字串中間會自動插入一個空白鍵:
```python
>>> print("123", "456", "789")
123 456 789
```
如果傳入 `print()` 函式中的東西不是字串,而是文字或是布林值等等其他東西, `print()` 函式會先偷偷呼叫 `str()` 函式把這些東西都轉成對應的字串之後,才印出他們:
```python
>>> print(123, 1000000000000000000000000000000.0, 1 == 1)
123 1e+30 True
```
有了 `input()` 函式與 `print()` 函式,我們的程式就俱備了因應不同使用者需求的能力:
```python
# hello.py
name = input("What's your name? ")
print("Hello,", name)
```
```shell
$ python3 hello.py
What's your name? kinoe
Hello, kinoe
$ python3 hello.py
What's your name? 乂卍↘㊣煞氣a路人甲㊣↖卍乂
Hello, 乂卍↘㊣煞氣a路人甲㊣↖卍乂
```
到現在,你已經學會如何做計算、如何做決策,以及如何取得使用者的輸入並輸出計算結果了。你現在已經可以寫出小型的工具讓別人使用了!但只有這些功能,我們的程式一次就只能處理一個小工作,如果有很大量的資料需要進行同樣的處理,我們就需要手動執行程式很多次。為了讓程式能夠自動處理重複的工作,我們需要學習更進階的流程控制工具。因此,在下一章當中,我們將要來學習一個強大的流程控制工具 —— 迴圈。
## Excersices
### 3.1 鮭魚之亂
[鮭魚之亂](https://zh.wikipedia.org/zh-tw/%E9%AE%AD%E9%AD%9A%E4%B9%8B%E4%BA%82)是指因壽司郎在臺灣的促銷活動,而在臺灣社會引發的一系列話題。根據壽司郎所訂定的規則,**姓名中正好有「鮭魚」兩字者可免費用餐**。請問下列哪些人可以免費吃壽司郎呢?
(A) 陳鮭魚
(B) 蔡蠵龜
(C\) 張鮭魚之夢
(D) 陳政成有震天龍砲變身基隆最專情於二零二一三月十四日與孔安穩定交往中愛妳愛一生一世此生想帶妳一起吃鮭魚
(E) 吳郭魚
<!-- 參考解答
```python
>>> name = "陳鮭魚"
>>> "鮭魚" in name
True
>>> name = "蔡蠵龜"
>>> "鮭魚" in name
False
>>> name = "張鮭魚之夢"
>>> "鮭魚" in name
True
>>> name = "陳政成有震天龍砲變身基隆最專情於二零二一三月十四日與孔安穩定交往中愛妳愛一生一世此生想帶妳一起吃鮭魚"
>>> "鮭魚" in name
True
>>> name = "吳郭魚"
>>> "鮭魚" in name
False
>>> # Ans: (A), (C), (D)
```
-->
### 3.2 剪刀石頭布
又到了買晚餐的時間了,猜拳輸的人要去買晚餐。給定我出的拳 `my_handsign` 和我弟出的拳 `brothers_handsign`,請幫我判斷我今天需不需要去買晚餐?(註:不考慮平手的狀況)
(A) `my_handsign = "剪刀"`, `brothers_handsign = "石頭"`
(B) `my_handsign = "剪刀"`, `brothers_handsign = "布"`
(C\) `my_handsign = "布"`, `brothers_handsign = "石頭"`
(D) `my_handsign = "石頭"`, `brothers_handsign = "剪刀"`
<!-- 參考解答
```python
>>> my_handsign = "剪刀"
>>> brothers_handsign = "石頭"
>>> my_handsign == "剪刀" and brothers_handsign == "石頭" or my_handsign == "石頭" and brothers_handsign == "布" or my_handsign == "布" and brothers_handsign == "剪刀"
True
>>> my_handsign = "剪刀"
>>> brothers_handsign = "布"
>>> my_handsign == "剪刀" and brothers_handsign == "石頭" or my_handsign == "石頭" and brothers_handsign == "布" or my_handsign == "布" and brothers_handsign == "剪刀"
False
>>> my_handsign = "布"
>>> brothers_handsign = "石頭"
>>> my_handsign == "剪刀" and brothers_handsign == "石頭" or my_handsign == "石頭" and brothers_handsign == "布" or my_handsign == "布" and brothers_handsign == "剪刀"
False
>>> my_handsign = "石頭"
>>> brothers_handsign = "剪刀"
>>> my_handsign == "剪刀" and brothers_handsign == "石頭" or my_handsign == "石頭" and brothers_handsign == "布" or my_handsign == "布" and brothers_handsign == "剪刀"
False
```
-->
### 3.3 絕對值
一個數字 $x$ 的絕對值 $|x|$ 的定義為:
$$
|x| =
\begin{cases}
x, & x \geq 0, \\
-x, & x < 0.
\end{cases}
$$
給定一個整數 `x` ,請印出 `x` 的絕對值。
<!-- 參考解答
```python
>>> x = 10
>>> if x >= 0:
... print(x)
... else:
... print(-x)
...
10
>>> x = -100
>>> if x >= 0:
... print(x)
... else:
... print(-x)
...
100
>>> x = 0
>>> if x >= 0:
... print(x)
... else:
... print(-x)
...
0
>>>
```
-->
### 3.4 二十四節氣
[二十四節氣](https://zh.wikipedia.org/zh-tw/%E8%8A%82%E6%B0%94),是中國古代根據太陽的運行位置區分出來,用來指導農事之曆法曆注。現代人已經可以利用太陽在黃道上的位置精確的定義出每年當中各個節氣的日期,列表如下:
|日期|節氣|
|-|-|
|1 月 6 日|小寒|
|1 月 20 日|大寒|
|2 月 4 日|立春|
|2 月 19 日|雨水|
|3 月 6 日|驚蟄|
|3 月 21 日|春分|
|4 月 5 日|清明|
|4 月 20 日|穀雨|
|5 月 6 日|立夏|
|5 月 21 日|小滿|
|6 月 6 日|芒種|
|6 月 21 日|夏至|
|7 月 7 日|小暑|
|7 月 23 日|大暑|
|8 月 8 日|立秋|
|8 月 23 日|處暑|
|9 月 8 日|白露|
|9 月 23 日|秋分|
|10 月 8 日|寒露|
|10 月 23 日|霜降|
|11 月 7 日|立冬|
|11 月 22 日|小雪|
|12 月 7 日|大雪|
|12 月 22 日|冬至|
請撰寫一個腳本檔,讓使用者輸入月份 `month` 和日期 `date` ,並輸出該日是哪一個節氣。若該日什麼節氣都不是,就輸出「普通的一天」。
(:對了你知道節氣看的是國曆嗎?)
(Σ(゚Д゚)!!)
<!-- 參考解答
```python
month = int(input("請輸入月份: "))
date = int(input("請輸入日期: "))
if month == 1:
if date == 6:
print("小寒")
elif date == 20:
print("大寒")
else:
print("普通的一天")
elif month == 2:
if date == 4:
print("立春")
elif date == 19:
print("雨水")
else:
print("普通的一天")
elif month == 3:
if date == 6:
print("驚蟄")
elif date == 21:
print("春分")
else:
print("普通的一天")
elif month == 4:
if date == 5:
print("清明")
elif date == 20:
print("穀雨")
else:
print("普通的一天")
elif month == 5:
if date == 6:
print("立夏")
elif date == 21:
print("小滿")
else:
print("普通的一天")
elif month == 6:
if date == 6:
print("芒種")
elif date == 21:
print("夏至")
else:
print("普通的一天")
elif month == 7:
if date == 7:
print("小暑")
elif date == 23:
print("大暑")
else:
print("普通的一天")
elif month == 8:
if date == 8:
print("立秋")
elif date == 23:
print("處暑")
else:
print("普通的一天")
elif month == 9:
if date == 8:
print("白露")
elif date == 23:
print("秋分")
else:
print("普通的一天")
elif month == 10:
if date == 8:
print("寒露")
elif date == 23:
print("霜降")
else:
print("普通的一天")
elif month == 11:
if date == 7:
print("立冬")
elif date == 22:
print("小雪")
else:
print("普通的一天")
elif month == 12:
if date == 7:
print("大雪")
elif date == 22:
print("冬至")
else:
print("普通的一天")
```
-->
----
[<< Lec 02 - 運算式](https://hackmd.io/@kaeteyaruyo/python-programming-02) | [目錄](https://hackmd.io/@kaeteyaruyo/python-programming-index) | [Lec 04 - 迴圈 >>](https://hackmd.io/@kaeteyaruyo/python-programming-04)