[python] [VI coding] 第七章 迭代 - 教學區

[python] [VI coding] 第七章 迭代

特種兵

特種兵圖像(預設)

2020-11-25 15:27:53

From:211.23.21.202

第七章 迭代

7-1 notepad++ 的設定

這邊要分享的設定全部都在 alt 功能表 -> 設定 -> 偏好設定 內

  1. 一般 -> 關閉最後一個文件時關閉程式
    預設是關完所有用 notepad++ 開的檔案還會留下 notepad++ 程式,必須按 alt+f4 才會關閉視窗
  2. 編輯 -> 換行設定 / 顯示行號
    顯示行號的話用點顯器或聽讀都感受不到,可以說是給明眼人看的。
    視障者如果真的需要顯示行號就去把 NVDA 功能表 -> 偏好設定 -> 設定 -> 文件格式 -> 行號打勾
  3. 開新文件 -> windows cr / UTF-8 / 套用於 ANSI 檔案
    因為我的 git 還有網站的正式環境都是 linux, 所以會選 unix lf 換行符號,以求統一,如果都是 windows 環境就選上列所建議
    中文的話,現在應該都是用 utf-8 編碼吧
  4. 最近使用的檔案 -> 程式啟動時不檢查 / 僅檔案名稱
    這樣可以加快開檔速度,乾淨的開我指定的這個檔案就好了
    除了顯示檔案名稱也可以顯示完整路徑,就看個人習慣
  5. 副檔名連結設定
    看有沒有什麼副檔名是直接按 enter 就想用 notepad++ 開的可以設定
  6. 程式語言 -> 跳格設定
    就是按 tab 實際上畫面會空幾個空白之類的設定
    我是習慣 4 個空格但顯示的是一個 tab 符號,如果真的顯示空格的話摸點字會比較辛苦
  7. 備份 -> 開啟程式時繼續上次的工作階段
    一樣是看習慣,有人覺得方便,有人覺得干擾
  8. 字詞自動完成功能 -> 啟動自動完成功能
    例如打了 ran 可以讓你用方向鍵選 rand, random 等等,要搭配 NVDA 的附加元件
    這些建議的字是你打過的或者這個檔案出現過的
    並沒有像 ide 一樣會幫你整理有哪些 function 可用那種功能
    也可以設定自動補齊後括號的功能
  9. 多重實體開啟 -> 多重實體開啟模式
    多重實體就是用 notepad++ 開多檔時是開新視窗 alt+tab 切換
    預設的話是用新分頁開 ctrl+tab 切換
  10. 其他
    其實每個設定都看一下,或許會有你需要或喜歡的
    通常是自己常用的軟體,我一定會做這件事

7-2 安裝 notepad++ 附加元件

使用 NVDA 的視障者有需要 自動補齊 功能就一定要安裝。當然還有其他附加功能。

上面的設定雖然打勾了,但那個補齊介面原生 NVDA 無法支援。

詳細的附加元件操作可以看這篇 Notepad++ 編輯器增強 附加元件

7-3 檔案編碼

使用 notepad++ 開啟檔案應該是 utf-8 編碼,但更保險一點的做法是開新檔時自己選擇一次 utf-8 編碼。

特別是使用 notepad++ 去開啟之前建立或以記事本開啟的檔案,都要注意一下:

  1. alt 功能表
  2. 往右找到「編碼」
  3. 往下找到「轉換至 UTF-8 碼格式」按 enter

只要在第一次開檔時做一次就可以了,我在撰寫程式時很少建立新檔,通常都是複製之前舊的 utf-8 檔案,清空內容改名後就開始寫程式,

如果是這樣的話,就不需要再做這個設定了。建議大家都用 utf-8 編碼來撰寫程式比較不會出現中文亂碼問題。

7-4 什麼是迭代

迭代就是重複執行一個程式碼區塊敘述的過程。實現迭代可以有很多方式,我們看過了 遞迴for 迴圈

接下來我們要看一下 while 迴圈。但在這之前,我們先來說一下關於賦值給變數的一些觀念。

7-5 重新賦值

你可能已經發現,在 python 中可以合法的重複賦值給同一個變數。

當你賦一個新值給已存在的變數,同時該變數也會停止對於舊值的引用。

>>> x = 5
>>> x
5
>>> x = 7
>>> x
7

一開始我們給 x 變數 5, 後來又改成 7。讓我們來釐清一下等號給我們的一些錯覺。

在 python 中使用 = 來賦值,也就是把右邊的值指派給左邊,僅僅如此而已。

不要把等號跟數學的等於連想在一起。首先,對數學而言,等於代表兩邊都相等,

而在 python 中,x = 7 但 7 = x 是錯誤的語法。

再來,對數學而言,等於是恆等式,可是 python 不一定。看一下這個:

>>> a = 5
>>> b = a    # b 和 a 相等
>>> a = 3    # b 和 a 不再相等
>>> b
5

從上面的例子可以看出,我們改變 a 的值,但並不會順便把 b 都改了,所以 a 不恆等於 b。

他們兩個變數的記憶體位置是不同的,所以雙方的值並不會互相影響。我覺得把等號理解成將左邊這個標籤指向右邊的值會比較好,

也就是說,右邊的常數假設都存在於這個世界中,我們只是暫時把左邊的標籤貼給右邊的而已。

右邊的值可以被貼很多張標籤,而標籤也可以隨時撕下來改貼到別的地方去。

雖然重複指派不同的值給同一個變數有時很方便,但必須小心使用這樣的方式,

因為這樣做可能讓你的程式不容易理解或閱讀,並且增加了發生錯誤的可能性,在除錯上也比較困難。

7-6 變數更新

常見的重複賦值情況是參考原本的值後更新變數的新值,如下:

>>> x = x + 1

等號的優先權其實很低,先看等號右邊,將 x 的值加一後把新值再次指派給 x。

因此如果你對一個不存在的變數更新其值,你會得到一個錯誤,因為 python 會先檢查等號右邊,發現 x 並不存在。

>>> x = x + 1
NameError: name 'x' is not defined

所以,更新變數的值時,一定要先初始化這個變數,通常我們會簡單的這樣做:

>>> x = 0
>>> x = x + 1

增加一叫作 遞增 而 減少一就叫作 遞減

7-7 while 敘述

電腦擅長重複執行任務,並且結果非常可靠,這樣的動作又稱為 迭代

迭代是很普遍的行為,python 很容易做到,一個是 for 迴圈,這個我們稍後會再深入它,

另外一個就是 while 迴圈了,以下這是之前第五章 countDown 函數的 while 版本:

def countDown(n):
	while n > 0:
		print(n)
		n = n - 1
	print('爆炸')

當 n 大於 0 時就列印它的值並遞減,直到 不大於 0 時,進入迴圈的條件為假,所以印出 爆炸

以下是 while 迴圈的執行流程:

  1. 確定進入迴圈的條件是 True 還是 False
  2. 條件為 False 時跳出整個 while 區段並執行 while 區塊之外的敘述。
  3. 條件為 True 時進入 while 迴圈執行其程式區塊,然後返回第一個步驟。

這種運作方式被稱為 迴圈,因為執行完第三步又會回到第一步。

在迴圈的本體會試著改變多個變數的值,使其進入迴圈的條件最終為 False,

這樣才能離開迴圈的執行,否則將成為無限的循環,這稱為 無窮迴圈

對於 countDown 函數,我們每次在 while 迴圈本體中減一,直到它等於 0 或小於 0 時離開迴圈。

但有些 while 迴圈的條件不容易分辨是否一定不會造成無窮迴圈:

def sequence(n):
	while n != 1:
		print(n)
		if n % 2 == 0:        # n 是偶數
			n = n / 2
		else:                 # n 是奇數
			n = n * 3 + 1

上面的函數,當 n 不等於 1 就進入迴圈,而當 n 等於 1 則條件為 False 離開 while 迴圈。

每次進入迴圈會先印出 n 的值,讓我們便於確認現在 n 的值,最主要是想知道 n 是偶數還是奇數。

如果是偶數,n 就除以 2如果是奇數,n 就乘以 3 再加 1。

我們使用參數 3 帶入 sequence 得到的所有 n 值如下:

3, 10, 5, 16, 8, 4, 2, 1

從這個案例中,由於 n 有時遞增又有時遞減,因此我們很難一下子就斷定 n 最後是否一定會等於 1。

但當 n 為某些值時,我們能證明它最後一定會回到 1 這個值。例如 n 是 2 的次方且每次除以 2 後都會得到偶數。

困難的是目前這世界上還沒有人能夠證明 n 為正數時最後都能回到 1 上。

7-8 break 中斷敘述

終止迴圈的方式,除了進入迴圈判斷式的值為 False 外,還可以在迴圈中設定達到某個條件,就強制跳開迴圈,

如果是後者,就必須使用 if 搭配 break 敘述。

舉個例子,假設你希望讓使用者一直輸入,直到輸入 done 才離開迴圈繼續動作時,可以這樣寫:

while True:
	line = input('> ')
	if line == 'done':
		break
	print(line)

因為 while 的進入條件為 True 所以進入該迴圈,讓使用者在大於符號後面輸入任何文字,

如果不是輸入 done 就印出其內容並返回迴圈頂部再度循環。

若輸入 done 則執行 break 敘述中斷迴圈並繼續執行迴圈之外的其他敘述。

這樣的寫法很常見,這使你可以直接進入 while 迴圈,當想離開時可以隨時在迴圈的任何地方加上 break 的條件,

讓你的 while 迴圈條件變得絕對,但請切記使用這樣的迴圈一定要加入中斷條件,否則程式將進入 無窮迴圈 而無法停止。

7-9 continue 跳回敘述

在迴圈當中,如果某種條件成立後,我們希望跳過後面的敘述直接回到迴圈的判斷條件再繼續執行。

這樣的需求可以使用 if 搭配 continue 敘述來完成。

假設有一個自動改選擇題作業的程式,當答對時給10分,答錯時印出錯誤的答案且不給分數,看看以下範例:

範例 7-1
answers = 'cbadcbdabc' # 正確答案
answer = 'cbdacddbbc' # 學生的答案
num = sum = 0 # 題數與總分

print('正確答案是', answers)
for num in range(len(answers)):
	if answer[num] != answers[num]: # 答錯就印出錯的答案且回到迴圈開頭
		print('第 ', num + 1, '題答錯:', answer[num])
		continue
	sum += 10 # 答對就累加 10 分
print('得到 ', sum, '分') # 迴圈結束後印出總得分

7-10 演算法

演算法是一種計算的方法,好的演算法可以說是一種速算的方法。

像九九乘法表就不是一種演算法,因為我們是直接記下它的答案。

如果你想偷懶,像是某個數加上 9 可能不好算,但你可以先算加 10 再減 1反而會比較快。

總之,推導演算法很無聊,但設計演算法很有趣,可以說是一種智力挑戰。

事實上,如果可以找出規則而設計出一些演算法,對於撰寫程式與提升程式效能很有幫助。

7-11 迴圈也有 else 敘述

我們已經知道在 if 敘述可以使用 else 來當 if 條件為 False 時執行的程式區塊。

事實上迴圈也可以使用 else 敘述,不管是 for 還是 while 都適用。

其實它與在 if 時使用的條件一樣,也就是當迴圈進入條件為 False 時就會離開迴圈並執行 else 的程式區塊。

要注意的是,迴圈進入條件的第一圈判斷為 False 也會執行 else 的部分。反過來說,當迴圈中使用了 break 強制離開就不會去執行 else 的部分。

使用的時機就是當迴圈條件為 False 需要做某些事就能用 else 敘述。要特別提醒的是迴圈順順跑完最後條件也是會變成 false 的,所以也會去執行 else 內的程式碼。

這裡是一個練習英文單字遊戲的程式片斷,如果單字中含有3個以上(包含) 的 aeiou 子音字母,就會印出錯誤訊息,

否則就給出通過訊息,但不管有幾個子音字母,最後都會印出遊戲結束的字樣。

sum = 0
for letter in word:
	if letter == 'a' or letter == 'e' or letter == 'i' or letter == 'o' or letter == 'u':
		sum += 1
	if sum > 2:
		print('該單字超過2個子音字母')
		break
else:
	print('通過')
print('遊戲結束')

我們這邊只是舉例使用迴圈與 else 的時機,先不管是否有更好的處理方式。使用 word 變數來帶入一些單字,測試結果。

程式遍歷整個 word 字串,當 word 裡的字母出現 a, e, i, o, u 其中一個就累加 sum 變數的值。

迴圈的結尾都會判斷 sum 是否已經大於 2 了,如果是就印出錯誤訊息並強制離開迴圈,這樣的情況下迴圈的 else 不會被執行。

如果遍歷完 word 而 sum 沒有超過 2, 那就表示這個單字沒有超過規定的子音字母數量,

此時會執行 else 的程式區塊,也就是印出通過訊息。

因為當遍歷完 word 就表示 for 迴圈的條件為 False, 因此會執行 else 程式區塊。

除錯

隨著撰寫的程式越來越大,發生錯誤的機率與次數也愈來愈多,花在除錯的時間也會變長。

這裡介紹一個除錯的方法,叫作 二分除錯法

當你寫了一個 100 行的程式,每次都從第 1 行開始除錯,你需要 100 個步驟來完成這件事。

不妨考慮把問題分成兩邊,在中間進行除錯,例如印出變數的值,

如果發現有錯,則問題一定在前 50 行就已經發生,否則問題會落在 51 到 100 行之間。

理論上重複做了 6 次左右就可以很精確的把問題點縮小到數行的程式碼間。

當然,直接從中間的行數去尋找錯誤點是不實際的。比較好的方式是根據你的程式來思考,

去尋找可能發生錯誤的地方加以確認是比較有效率的,例如某段程式碼的運算比較複雜,因此出錯的機率就相對比較高。

所以當程式有問題時應該從最有可能的地方下手除錯會比較有效率。

動動腦

1.思考以無窮迴圈的設計方式如何求出一個數的正有理數平方根。(不能使用任何函數)

2.我們知道同樣功能的程式往往因為撰寫者的思考方式不同,會有很多種寫法,這也是程式有趣的地方之一。

上例計算選擇題答案的程式範例 7-1,試著改寫成不使用 continue 與 break 的版本看看。

練習

第1題 檔名 7-1.py

有個內建函數名為 eval,它的功能是將字串的算式進行一般算式的演算並返回答案。像這樣:

>>> eval('1 + 2 * 3')
7
>>> import math
>>> eval('math.sqrt(5)')
2.2360679774997898
>>> eval('type(math.pi)')
<class 'float'>

寫一個函數名為 evalLoop, 功能是讓使用者一直輸入算式,每次都用 eval 計算出結果並印出來,

直到使用者輸入 quit 才離開。

第2題 檔名 7-2.py 檔名: 7-2p.py

寫一個函數,接受一個整數參數,功能是列出這個數的標準分解式。

至少可以做到這樣:

>>> factorization(100)
2*2*5*5

進階練習就是整理上面的式子成為真正的標準分解式,但這部分會有一點難度,有興趣的人可以挑戰一下:檔名 7-2p.py

>>> factorization(100)
2^2 * 5^2

我們這邊先暫時以 ^ 代表次方的符號。

第3題 檔名 7-3.py

寫一個讓使用者猜三位數的猜數字遊戲,先將答案設定成一個固定的值,例如 312,

接著開始讓使用者輸入答案,如果輸入的不是三位數就印出提醒,猜錯也要印出太大或太小的提示,並且持續讓使用者猜到答對為止。

唯一的規定是一定要用無窮迴圈的方式來撰寫這個程式。

參考資料

python 的物件與記憶體管理

python loop else

統計列表

平方根1

平方根2

平方根3

牛頓定理

最後更新:2020-11-28 15:58:43

From: 211.23.21.202

By: 特種兵