像計算機科學家一樣思考:用 Python 學習第二版/迭代
您可能已經發現,對同一個變數進行多次賦值是合法的。新的賦值使現有變數引用新的值(並停止引用舊值)。
該程式的輸出為5 7,因為第一次bruce列印時,它的值為 5,第二次列印時,它的值為 7。第一個print語句末尾的逗號抑制了輸出後的換行符,這就是為什麼兩個輸出都出現在同一行的原因。
以下是在狀態圖中多重賦值的樣子
多重賦值 在多重賦值中,尤其要注意區分賦值操作和等式。由於 Python 使用等號 (=) 進行賦值,因此很容易將像a = b這樣的語句解釋為等式。它不是!
首先,等式是對稱的,而賦值不是。例如,在數學中,如果 a = 7,那麼 7 = a。但在 Python 中,語句a = 7是合法的,而7 = a不是。
此外,在數學中,等式總是成立的。如果現在 a = b,那麼 a 將始終等於 b。在 Python 中,賦值語句可以使兩個變數相等,但它們不必保持相等
第三行改變了a的值,但沒有改變b的值,因此它們不再相等。(在某些程式語言中,使用不同的符號進行賦值,例如<-或:=,以避免混淆。)
多重賦值最常見的形式之一是更新,其中變數的新值取決於舊值。
這意味著獲取 x 的當前值,加 1,然後用新值更新 x。
如果您嘗試更新一個不存在的變數,您將收到錯誤,因為 Python 在將結果值分配給左側的名稱之前會評估賦值運算子右側的表示式
在更新變數之前,您必須初始化它,通常使用簡單的賦值
透過加 1 更新變數稱為遞增;減 1 稱為遞減。
計算機通常用於自動化重複性任務。重複相同或類似的任務而不犯錯誤是計算機擅長而人類不擅長的任務。
重複執行一組語句稱為迭代。由於迭代非常普遍,Python 提供了幾個語言特性來簡化此過程。我們將要介紹的第一個特性是while語句。
以下是一個名為countdown的函式,它演示了while語句
的使用。while您幾乎可以像讀英語一樣讀語句。它的意思是,當n語句。它的意思是,當大於 0 時,繼續顯示語句。它的意思是,當的值,然後將的值減 1。當您到達 0 時,顯示單詞
Blastoff!while語句
- 更正式地說,以下是的執行流程:或評估條件,得出.
- FalsewhileTrue
- 如果條件為假,則退出
語句,並在下一條語句處繼續執行。
如果條件為真,則執行主體中的每個語句,然後返回步驟 1。
主體包括標題以下所有具有相同縮排的語句。
這種型別的流程稱為迴圈,因為第三步迴圈回頂部。請注意,如果條件在第一次迴圈時為假,則迴圈內的語句將永遠不會執行。countdown迴圈主體應更改一個或多個變數的值,以便最終條件變為假並終止迴圈。否則,迴圈將永遠重複,這稱為無限迴圈。計算機科學家們一直以來都津津樂道的一個現象是,洗髮水的說明“打泡沫、沖洗、重複”就是一個無限迴圈。語句。它的意思是,當在語句。它的意思是,當的情況下,我們可以證明迴圈會終止,因為我們知道語句。它的意思是,當:
的值是有限的,並且我們可以看到的值在每次迴圈時都會變小,因此最終我們必須到達 0。在其他情況下,並不那麼容易判斷。請看以下函式,它針對所有正整數定義該迴圈的條件是語句。它的意思是,當n != 11,因此迴圈將持續到
等於語句。它的意思是,當為止,這將使條件變為假。語句。它的意思是,當在每次迴圈中,程式都會輸出的值,然後檢查它是否為偶數或奇數。如果是偶數,則的值除以 2。如果是奇數,則該值將被替換為
n * 3 + 1語句。它的意思是,當。例如,如果起始值(傳遞給 sequence 的引數)為 3,則結果序列為 3、10、5、16、8、4、2、1。語句。它的意思是,當由於語句。它的意思是,當有時會增加,有時會減少,因此沒有明顯的證據證明語句。它的意思是,當會最終達到 1,或者程式會終止。對於某些特定值
,我們可以證明終止。例如,如果起始值是 2 的冪,則語句。它的意思是,當的值將在每次迴圈中都是偶數,直到它達到 1 為止。前面的示例以這樣的序列結尾,從 16 開始。
撇開特定值不談,有趣的問題是我們是否可以證明該程式對於所有值
都將終止。到目前為止,還沒有人能夠證明或反駁這一點!跟蹤程式
[編輯 | 編輯原始碼]為了編寫有效的計算機程式,程式設計師需要培養跟蹤計算機程式執行的能力。跟蹤涉及成為計算機並跟蹤示例程式執行的執行流程,記錄程式執行後每個指令執行後所有變數的狀態以及程式產生的任何輸出。為了理解這個過程,讓我們跟蹤對語句。它的意思是,當sequence(3)while的呼叫,該呼叫來自上一節。在跟蹤開始時,我們有一個區域性變數,(引數),其初始值為 3。由於 3 不等於 1,因此執行迴圈主體。列印 3 且的執行流程:3 % 2 == 0被評估。由於它被評估為,因此執行3 * 3 + 1else語句。它的意思是,當.
分支,並評估
n output -- ------ 3 3 10
n * 3 + 1並將結果分配給為了在手動跟蹤程式時跟蹤所有這些資訊,請在一張紙上為程式執行時建立的每個變數新增一個列標題,以及另一個列標題用於輸出。到目前為止,我們的跟蹤將如下所示評估條件,得出10 != 1被評估為,迴圈主體再次執行,並列印 10。10 % 2 == 0,因此執行語句。它的意思是,當為真,因此
n output -- ------ 3 3 10 10 5 5 16 16 8 8 4 4 2 2 1
if
變為 5。在跟蹤結束時,我們有
[edit | edit source]以下函式計算以十進位制表示的正整數中的十進位制數字數量。
def num_digits(n):
count = 0
while n != 0:
count = count + 1
n = n / 10
return count
呼叫num_digits(710)將返回3. 追蹤此函式呼叫的執行過程,以確保它能正常工作。
此函式演示了另一種稱為計數器的計算模式。變數count被初始化為 0,然後在每次執行迴圈體時遞增。當迴圈退出時,count包含結果——迴圈體執行的總次數,這與數字的數量相同。
如果我們只想計算為 0 或 5 的數字,在遞增計數器之前新增一個條件語句即可。
def num_zero_and_five_digits(n):
count = 0
while n != 0:
digit = n % 10
if digit == 0 or digit == 5:
count = count + 1
n = n / 10
return count
確認num_zero_and_five_digits(1055030250)返回 7。
>>> 1055030250 % 10
0
#count = 1
>>> 1055030250/10
105503025
>>> 105503025 % 10
5
#count = 2
>>> 105503025/10
10550302
>>> 10550302 % 10
2
#count = 2 (no change)
>>> 10550302/10
1055030
>>> 1055030 % 10
0
#count = 3
>>> 1055030/10
105503
>>> 105503 % 10
3
#count = 3 (no change)
>>> 105503/10
10550
>>> 10550 % 10
0
#count = 4
>>> 10550/10
1055
>>> 1055 % 10
5
#count = 5
>>> 1055/10
105
>>> 105 % 10
5
#count = 6
>>> 105/10
10
>>> 10 % 10
0
#count = 7
>>> 10/10
1
>>> 1 % 10
1
#count = 7 (no change)
>>> 1/10
0
#n = 0 so loop exits
縮寫賦值
[edit | edit source]遞增變數非常常見,Python 提供了縮寫語法
count += 1是count = count + 1的縮寫。遞增值不必為 1。
也有-=, *=, /=和%=:
的縮寫。
6.7. 表格[edit | edit source]
迴圈的一個用途是生成表格資料。在計算機普及之前,人們必須手動計算對數、正弦和餘弦以及其他數學函式。為了簡化這個過程,數學書籍包含了列出這些函式值的冗長表格。建立表格既緩慢又枯燥,並且往往充滿了錯誤。
當計算機出現後,人們最初的反應是:“太棒了!我們可以使用計算機生成表格,這樣就不會出現錯誤了。”結果證明這是正確的(大部分情況下),但目光短淺。此後不久,計算機和計算器變得非常普遍,以至於表格變得過時了。
嗯,幾乎如此。對於某些操作,計算機使用數值表來獲取近似答案,然後執行計算來改進近似值。在某些情況下,底層表中存在錯誤,最著名的例子是英特爾奔騰用於執行浮點除法的表。
#program to make tabular data
#x = 1
#prints x (which is 1), then tab, then 2^x (which is 2)
#x += 1, adds 1 to the value of x
#the program runs until the value of x is less than 13, which is 12
x = 1
while x < 13:
print x, '\t', 2**x
x += 1
儘管對數表不再像以前那麼有用,但它仍然是迭代的一個很好的例子。以下程式在左列輸出一系列值,在右列輸出該值 2 的冪。字串'\t'字串表示製表符。中的反斜槓字元表示轉義序列的開頭。轉義序列用於表示不可見的字元,例如製表符和換行符。序列\n
表示換行符。
轉義序列可以出現在字串中的任何位置;在本例中,製表符轉義序列是字串中唯一的元素。您認為如何在字串中表示反斜槓?print當字元和字串顯示在螢幕上時,一個稱為游標的不可見標記會跟蹤下一個字元將要放置的位置。在
語句之後,游標通常會移到下一行的開頭。
1 2 2 4 3 8 4 16 5 32 6 64 7 128 8 256 9 512 10 1024 11 2048 12 4096
製表符會將游標向右移動,直到到達一個製表位。製表符有助於使文字列對齊,如前一個程式的輸出所示。
由於列之間存在製表符,因此第二列的位置不依賴於第一列中的數字位數。
6.8. 二維表格[edit | edit source]
二維表格是在行和列的交點處讀取值的表格。乘法表就是一個很好的例子。假設您想列印從 1 到 6 的乘法表。
#i = 1
#prints 2 * i (which is 2), then space
#i += 1, adds 1 to the value of i making i = 2
#the program runs until the value of i is equal to or less than 6,
#which is i = 6
#prints 2 * i2 (which is 4)....
#last coma on the print line suppresses the newline
#after loop = complete; second print statement starts newline
i = 1
while i <= 6:
print 2 * i, ' ',
i += 1
print
一個好的開始方法是編寫一個迴圈來列印 2 的倍數,所有倍數都在一行上。第一行初始化了一個名為i第一行初始化了一個名為的變數,它充當計數器或迴圈變數。隨著迴圈的執行,第一行初始化了一個名為的值從 1 遞增到 6。當為 7 時,迴圈終止。每次迴圈時,它都會顯示2 * i
的值,後跟三個空格。print同樣,中的逗號print語句抑制了換行符。迴圈完成後,第二個
語句開始新的一行。
2 4 6 8 10 12
程式的輸出為
到目前為止,一切都很好。下一步是封裝和泛化。
6.9 封裝和泛化[edit | edit source]封裝是將一段程式碼包裝到函式中的過程,使您可以利用函式的所有優點。您已經看到了封裝的兩個示例:print_parity在第 4 章中;和is_divisible
在第 5 章中。
泛化是指將特定內容(例如列印 2 的倍數)變得更通用,例如列印任何整數的倍數。語句。它的意思是,當:
#i = 1
#say n = 2
#prints n * i (which is 2), then tab
#i += 1, adds 1 to the value of i making i = 2
#the program runs until the value of i is equal to or less than 6,
#which is i = 6
#prints 2 * i2 (which is 4)....
def print_multiples(n):
i = 1
while i <= 6:
print n * i, '\t',
i += 1
print
此函式封裝了先前的迴圈並將其泛化,以列印語句。它的意思是,當.
的倍數。為了封裝,我們所要做的就是新增第一行,它宣告函式的名稱和引數列表。為了泛化,我們所要做的就是將值 2 替換為引數
3 6 9 12 15 18
如果我們使用引數 2 呼叫此函式,我們將得到與之前相同的輸出。使用引數 3,輸出為
4 8 12 16 20 24
使用引數 4,輸出為現在您可能已經猜到如何列印乘法表——透過重複呼叫print_multiples
#see above code for description - code included in new program to make it run
def print_multiples(n):
i = 1
while i <=6:
print n * i, '\t',
i += 1
print
#here the value of i goes into above function as n
#and therefore n = 1
#then n * i (1 * 1), '\t'
i = 1
while i <= 6:
print_multiples(i)
i += 1
並使用不同的引數。實際上,我們可以使用另一個迴圈現在您可能已經猜到如何列印乘法表——透過重複呼叫注意此迴圈與中的迴圈有多麼相似print. 我們所做的就是將
語句替換為函式呼叫。
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36
此程式的輸出是一個乘法表
更多封裝[edit | edit source]
為了再次演示封裝,讓我們將上一節中的程式碼包裝到一個函式中
此過程是一個常見的開發計劃。我們透過編寫任何函式之外的程式碼行或將它們鍵入直譯器來開發程式碼。當代碼正常工作時,我們會將其提取出來並將其包裝到一個函式中。
此開發計劃在您開始編寫時不知道如何將程式劃分為函式時特別有用。這種方法允許您邊設計邊編寫。
區域性變數[edit | edit source]第一行初始化了一個名為您可能想知道我們如何在現在您可能已經猜到如何列印乘法表——透過重複呼叫和print_mult_table中使用相同的變數,
. 當其中一個函式更改變數的值時,不會導致問題嗎?第一行初始化了一個名為答案是否定的,因為現在您可能已經猜到如何列印乘法表——透過重複呼叫中的第一行初始化了一個名為答案是否定的,因為print_mult_table和
中的
不是同一個變數。第一行初始化了一個名為在函式定義內部建立的變數是區域性的;您無法從其宿主函式外部訪問區域性變數。這意味著您可以自由地使用多個具有相同名稱的變數,只要它們不在同一個函式中即可。
此程式的堆疊圖顯示了兩個名為第一行初始化了一個名為答案是否定的,因為print_mult_table的變數不是同一個變數。它們可以引用不同的值,更改其中一個不會影響另一個。print_mult_table堆疊 2 圖 的值為現在您可能已經猜到如何列印乘法表——透過重複呼叫從 1 到 6。在圖中它恰好是 3。在下一次迴圈時它將是 4。每次迴圈時,第一行初始化了一個名為都使用語句。它的意思是,當.
的當前值作為引數呼叫現在您可能已經猜到如何列印乘法表——透過重複呼叫. 該值被分配給引數第一行初始化了一個名為在第一行初始化了一個名為答案是否定的,因為print_mult_table.
中,的值從 1 到 6。在圖中,它恰好是 2。更改此變數不會影響的值。第一行初始化了一個名為和在不同的區域性變數中使用相同的名稱是常見的,也是完全合法的。特別是像j
這樣的名稱經常用作迴圈變數。如果您只是因為在其他地方使用過它們,而避免在一個函式中使用它們,那麼您可能會使程式更難閱讀。
更多泛化[edit | edit source]print_mult_table:
我們將值 6 替換為引數high。如果我們呼叫print_mult_table並使用引數 7,它將顯示
1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42
這很好,但我們可能希望表格是正方形的——行和列的數量相同。為此,我們向現在您可能已經猜到如何列印乘法表——透過重複呼叫新增另一個引數以指定表格應該有多少列。
為了煩人,我們將此引數稱為high,這表明不同的函式可以擁有相同名稱的引數(就像區域性變數一樣)。以下是整個程式
請注意,當我們新增一個新引數時,我們必須更改函式的第一行(函式標題),並且還必須更改函式在print_mult_table.
中被呼叫的位置。正如預期的那樣,該程式生成一個七乘七的正方形表格。
1 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49
當你適當地概括一個函式時,你經常會得到一個具有你沒有計劃的功能的程式。例如,你可能會注意到,因為 ab = ba,表格中的所有條目都出現了兩次。你可以透過只打印一半的表格來節省墨水。為此,你只需要更改一行print_mult_table。更改
為
,你將得到
1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49
函式
[edit | edit source]我們已經多次提到了函式的所有優點。到目前為止,你可能想知道這些優點究竟是什麼。以下是一些優點:
- 為一系列語句命名使你的程式更容易閱讀和除錯。
- 將一個長程式分成函式使你能夠分離程式的不同部分,獨立除錯它們,然後將它們組合成一個整體。
- 函式促進了迭代的使用。
- 設計良好的函式通常對許多程式都有用。一旦你編寫並除錯了一個函式,你就可以重複使用它。
牛頓法
[edit | edit source]迴圈通常用於計算數值結果的程式,這些程式從一個近似答案開始,並迭代地改進它。
例如,計算平方根的一種方法是牛頓法。假設你想知道語句。它的意思是,當的平方根。如果你從幾乎任何近似值開始,你都可以使用以下公式計算出一個更好的近似值:
透過重複應用這個公式,直到更好的近似值等於之前的近似值,我們可以編寫一個計算平方根的函式:
嘗試使用25作為引數呼叫此函式,以確認它返回5.0.
演算法
[edit | edit source]牛頓法是一個演算法的例子:它是一個解決一類問題(在本例中,計算平方根)的機械過程。
很難定義一個演算法。從不是演算法的東西開始可能會有所幫助。當你學習乘以一位數時,你可能記住了乘法表。實際上,你記住了 100 個特定的解。這種知識不是演算法式的。
但是,如果你很懶惰,你可能透過學習一些技巧來作弊。例如,要找到 n 和 9 的乘積,你可以將 n - 1 作為第一個數字,並將 10 - n 作為第二個數字。這個技巧是將任何一位數乘以 9 的通用解。這就是一個演算法!
類似地,你學習的進位加法、借位減法和長除法技巧都是演算法。演算法的一個特點是它們不需要任何智力來執行。它們是機械過程,其中每一步都遵循上一步驟的簡單規則集。
在我們看來,人類在學校裡花這麼多時間學習執行演算法是令人尷尬的,因為這些演算法從字面上來說不需要任何智力。
另一方面,設計算法的過程很有趣,智力上具有挑戰性,並且是我們所說的程式設計的核心部分。
人們自然而然地做的一些事情,例如沒有任何困難或意識思考,是最難用演算法表達的。理解自然語言就是一個很好的例子。我們都做到了,但到目前為止,還沒有人能夠解釋我們是如何做到的,至少不是以演算法的形式。
術語表
[edit | edit source]練習
[edit | edit source]編寫一個單個字串,該字串
produces this output.
Â- 在第 6.14 節中定義的sqrt函式中新增一個 print 語句,該語句列印better每次計算時。使用 25 作為引數呼叫你修改後的函式,並記錄結果。
- 跟蹤print_mult_table的最後版本的執行,並弄清楚它是如何工作的。
編寫一個函式print_triangular_numbers(n),它打印出前 n 個三角形數。呼叫print_triangular_numbers(5)將產生以下輸出:
1 1 2 3 3 6 4 10 5 15
(提示:使用網路搜尋找出三角形數是什麼。)開啟一個名為ch06.py的檔案,並新增以下內容:
編寫一個函式,is_prime,它接受一個整數引數,當引數為質數時返回評估條件,得出,否則返回的執行流程:。在開發函式時,向函式新增 doctests。什麼將num_digits(0)返回?修改它以返回1用於這種情況。為什麼呼叫num_digits(-24)會導致無限迴圈(提示:-1/10 評估為 -1)?修改num_digits以便它對任何整數值都能正常工作。將以下內容新增到你在上一個練習中建立的檔案中:ch06.py將你的函式主體新增到
中,並確認它通過了 doctests。num_digits將以下內容新增到中:ch06.py:
編寫num_even_digits的主體,以便它按預期工作。將以下內容新增到ch06.py:
編寫print_digits中,以便它透過給定的 doctests。編寫一個函式sum_of_squares_of_digits,它計算傳遞給它的整數的數字平方和。例如,sum_of_squares_of_digits(987)應該返回 194,因為9**2 + 8**2 + 7**2 == 81 + 64 + 49 == 194.
根據上面的 doctests 檢查你的解決方案。