
《從零開始PYTHON3》第五講
上一節課重點學習了字符串,並且傳遞了一個重要的理念,就是程序要對開發人員自己和用戶都足夠友好。在這個過程中,利用字符串給出充分、完整、准確的提示是非常重要的一部分。
在Python可以處理的不同數據類型中,每種數據類型都有自己特色的運算方式,比如我們上一節課對比過的數字類型和字符串類型的運算:
#數值的運算
>>> 123*3
369
#字符串的運算
>>> "123"*3
'123123123'
兩者的計算方式截然的不同,又具有自己的特點和不同的應用場景。這里講這個例子,並不只是想讓大家復習上一講的課程。而是想讓大家思考一下計算機最擅長的工作是什么?
沒錯,相信大多數人都想到了,計算機最擅長的,一是計算,二是重復。至於繪圖、音樂、視頻等等所謂的高端應用,不過是計算、重復的各種復雜組合。
計算在第二、第三講我們已經說過很多了,后面還會涉及到更高級的一些計算類應用。“重復”則是今天要說到的重點。
While循環
第三講的時候我們學過了計算機執行順序的問題。
每個Python程序都是從第一行開始,順序執行,直到程序的最后一句。其中碰到函數定義的時候,會“定義一個函數”,而不是“執行一個函數”。函數真正執行會在函數被調用的時候。
While循環則是讓計算機對某一段的程序代碼在限定條件下重復執行的手段。我們來看一個簡單的例子來幫助我們理解:
i = 1
while i <= 999:
i = i + 1
第一行,我們定義了一個變量i,並為它賦一個數字類型的值1。
第二行是while循環的條件部分,用於控制進入循環和繼續循環的條件。簡單說,就是當條件滿足的才開始循環,並且不斷循環下去,直到條件不再被滿足。
“<=”是條件,表示“小於等於”,同樣是因為計算機中沒有傳統用的“≤”符號,所以采用了變通的寫法。關於條件,或者說條件邏輯,我們會在后面詳細講解。
第三行是一條賦值語句,第二講我們講到變量的時候已經強調過,“=”是賦值操作符,表示把右側表達式的結果值,賦給左側的變量。
不要跟數學的等式弄混。在這里則是把i當前值,加1的計算結果,賦值給變量i,這時候i的值變成了新的值,也是剛才的計算結果。
我們是頭一次見到這種寫法,但只要弄明白這個是賦值語句,不是等式,你就不會困惑了。
這是一個極度簡化的循環模型,第一行可以稱為初始值,通常這個初始值應當滿足循環開始的條件;
第二行稱為循環的條件判斷,用於控制循環的開始和結束;
第三行稱為循環體,循環體應當是循環真正工作的部分,因為簡化,在這個例子中我們看不到有意義的工作。i=i+1則讓循環持續,並最終能夠不再滿足循環繼續的條件,從而退出循環。否則循環會永無止境的繼續下去,這被稱為“死循環”,也是計算機軟件“崩潰”、“死機”最常見的原因。
為了幫助理解,我們來看一個循環的流程圖。
流程圖是研究、分析程序結構的時候,公認非常有效的一種手段。建議你也學習畫,初學者不用糾結流程圖的樣式,而是用這種方式幫你分析程序邏輯方面的問題。

弄明白了循環的邏輯含義,我們再來看一下while循環語法上的特點,我們對比一下函數定義的語法:
#函數定義
def 函數名(參數):
函數體
#while循環
while 循環條件:
循環體
上面這種描述程序邏輯的方法,看起來結構清楚,能反映出來想描述的程序問題,但並不能執行。這種方式叫做“偽代碼”,是跟“程序流程圖”一樣用來分析、研究程序邏輯的方法,我們以后還會用到。
上面這個對比中,你能感覺到一些Python語法的邏輯規律。比如,都是用某個關鍵字開始,來引導整個程序塊,函數定義是用def,while循環是用while;接着是各自特色的東西,比如函數名、參數還有循環條件,相似的,都是是用冒號“:”來結尾第一行,並分割下面的函數體、循環體部分;后面甭管是函數體還是循環體,都是縮格書寫,縮格的結束代表整個程序塊的完成。
循環體中的賦值操作值得重點說一下。前面已經說過了,通過對可以影響循環條件的變量進行賦值,從而讓循環本身有機會退出循環,這是很重要的一個工作。這種賦值改變循環條件,幾乎在所有的循環中都會用到。所以這種通過對自身的改變完成對自身賦值的方式,又延伸出了一種簡易的寫法:
| 原有寫法: | 簡易寫法: |
|---|---|
| i = i + 1 | i += 1 |
| i = i -1 | i -= 1 |
| i = i * 2 | i *= 2 |
| i = i / 2 | i /= 2 |
請看上表中,左側是規范的寫法,右側是化簡后的寫法,使用起來會更方便。本質上,這是增加了+=、-=、*=、/=,四種運算賦值符,屬於保留字的一種。通常我們見到的加減乘除運算符都是一個字符,這里是2個字符,看起來不習慣而已。
練習時間
請使用while循環的方法,求整數1、2、3......直到100的和。
請先自己思考10分鍾,可以用流程圖或者偽代碼,有了比較明確的思路再向下看。
我們來看代碼:
#求整數1-100的和
#求和的結果保存在c,一開始是0
c = 0
#i保存整數1循環到100
i = 1
#進入及繼續循環的條件就是i<=100
while i <= 100:
c = c + i #求和一次
i = i + 1 #下一個整數,2/3/4...
#顯示結果
print("整數1-100的和為:",c)
程序中,我們使用了專門的一個變量c來保存累加的和,一開始沒有開始累加,所以c是0。變量i通過循環的方式,來模擬整數從1開始,每次加1,直到100的變化。循環的主體c=c+i,則是在每次循環中,進行一次求和的操作。最后縮格結束,表示循環的結束,使用print函數打印出來求和結果。運算結果是:
整數1-100的和為: 5050
作為練習,你可以試試把循環中的兩次賦值,用剛才講過的簡寫的方式來試試。
條件判斷(邏輯判斷)
對於一個循環來說,循環主體當然是循環的目的,所以通常也是循環的重點。但是在循環編寫的時候,仔細的思考和設置循環的開始條件和結束的條件,才是編寫循環的重點。而條件,通常都是由“邏輯判斷”來完成的。
不同於數字和字符串的千變萬化,邏輯條件只有“符合條件”和“不符合條件”兩種情況。
前者的同義詞可以為“是”、“真”、“對”,后者的同義詞是“否”、“假”、“錯”。
這種只有兩個值的類型,叫“布爾類型”,相關的運算叫“布爾運算”。在Python中,提供了數字(Number)類型的子類型(bool)來代表此類數據。bool類型只有兩個值,True表示真,False表示假。
因為bool是Number的子類型,所以如果把bool套用到Number類型中,1就代表真,0就代表假。我們用表格把這種關系再加深一下印象:
| 符合條件 | 不符合條件 |
|---|---|
| 真 | 假 |
| true | false |
| 1 | 0 |
| "abc"(有內容) | ""(無內容,空串) |
表格中最后一行,是字符串類型。對應到bool類型的情況,通常應當比較少用。但你得知道,如果字符串為空代表False,有字符,甭管是什么字符,都是True。
既然布爾類型,同數字、同字符串,都有對應的關系,有必要單獨獨立出來一種數據類型來增加學習量嗎?還真的是很有必要,我們來看一個例子。
比如在一個學生信息表中,在性別一欄,我們可以使用“男”、“女”,也可以用數字1、2。看起來也可以很完美的實現我們的目的,但其實這種做法很不可取。
你想象一下在表格的輸入中,有人輸入成了“男生”,意思沒有變,但這一點小的改變,可能讓計算機無所適從。比如數字敲錯成了“3”,計算機同樣也就無法知道這代表的究竟是什么性別。
如果使用布爾變量,isMan=True代表男生,剛才碰到的那些問題,都不會出現。
此外布爾運算作為數學中重要的一個分支,有完備的理論體系,在計算機中也有計算速度快、兼容性好的優點。
剛才講的是“邏輯”的表達方式,下面看看邏輯判斷的方式:
| 比較運算符 | 含義 |
|---|---|
| > | 大於 |
| >= | 大於等於 |
| < | 小於 |
| <= | 小於等於 |
| == | 等於(注意同賦值操作=區分) |
| != | 不等於 |
上表中,大於、大於等於、小於、小於等於都好記。邏輯相等的判斷,要跟賦值操作的等號區別開,因為這是完全不同的運算符,或者說是不同的Python關鍵字。
不等於符號,同樣是由於計算機中沒有“≠”符號的原因進行了合理的變化。這些都是運算符,運算符不一定只有一個字符。
下面我們來看幾個邏輯判斷的例子:
| 邏輯判斷表達式 | 結果 |
|---|---|
| 1 < 2 | |
| 1 > 2 | |
| 2.2 != 2.1 | |
| "a" > "b" | |
| "bcd" < "bd" | |
| a="hello" | |
| a == "hello" | |
| 2 = 2 |
請思考后,在本講結尾看答案。
除了這些常用的邏輯判斷,Python還有自己的特色的一種判斷方式,叫做連續判斷,這是其它常見的程序語言不具備的:
| 邏輯判斷表達式 | 結果 |
|---|---|
| 1 < 2 < 7 | True |
| 1 > 2 > 1 | False |
連續判斷經常用於對一個數據進行范圍界定性判斷,所以經常也被稱為“范圍判斷”。
挑戰:棋盤麥粒問題
在古代有一個國王,他擁有至高無上的權力和難以計數的財富。但是權力和財富最終使他對生活感到厭倦,渴望着有新鮮的刺激。
某天,一位老人帶着自己發明的國際象棋來朝見。國王對這新奇的玩意非常喜歡,非常迷戀,並感到非常滿足。
於是對老人說:“你給了我無窮的樂趣。為了獎賞你,你可以從我這兒得到你所要的任何東西”。
老人的要求是:請您在棋盤上的第一個格子上放1粒麥子,第二個格子上放2粒,第三個格子上放4粒,第四個格子上放8粒……即每一個次序在后的格子中放的麥粒都必須是前一個格子麥粒數目的倍數,直到最后一個格子放滿為止。
國王哈哈大笑,慷慨地答應了老人這個卑微的請求。
然而,國王最終發現,按照與老人的約定,全國的麥子竟然連棋盤一小半格子數目都不夠。
老人索要的麥粒數目實際上是天文數字,總數將是一個十九位數,折算重量約為2000多億噸,即使現代,全球小麥的年產量也不過是數億噸。
我們要使用while循環作為主體,來幫助國王算一下,放滿一個國際象棋棋盤,究竟需要多少粒麥子。
同樣,請先仔細進行思考,可以使用流程圖或者偽代碼的方式,有了比較清晰的思路再向下看。如果只是看看答案,缺少了思考,你很難真正掌握一門編程語言。
看起來很長的一個問題,其實用程序解決起來無比的容易。當然對於初學者來講,有一個清晰的思路比什么都重要。不然就好像看心靈雞湯文,看了很多的道理,但仍然過不好這一生。
這里介紹一種很常用的設計方法,叫做“快速原型法”。快速原型法其實並不是指什么特定的概念。其核心思想是,把需求先弄清楚,在整理需求的過程中,通過常識性的思考,快速的把已知的部分羅列出來,不能很快弄明白的可以先空着,直到最后,逐步將空白的部分填補完成,這時候形成的其實是偽代碼。最后用程序代替所有的偽代碼,形成最終的結果。
說起來容易,真正實踐起來,還是需要很多的經驗磨礪,才能得心應手。不過你先有一個概念就好,逐步的多讀程序,多練習編寫程序,就能做到。
下面我們也嘗試用這種方法來編寫這個程序:
1.理清需求。我們直接把需求寫到程序的注釋中:
"""
國際象棋有8行8列共64格,
第1個格子放1粒麥子,第2個格子放2粒麥子,
以后每格都比前面格子數量多一倍,
求麥子總數。
"""
你看,那么長的文字,真正理順弄清楚,真正對程序有影響的,並不多。
2.想想在循環之前,我們都應當有什么初始的值要先考慮?
#定義一個變量來保存總的麥子數量,開始為0
c=0
#定義一個變量,循環1-64,來代表每一個格子
i=1
#假設每個格子中的麥子數量為x,初始也是1
x=1
這一步就相當於循環的初始值,雖然只有i是用來控制循環,但既然我們用循環來幫助運算,那每一個跟循環有關的變量,不可能沒有初始值。
3.循環的過程部分,思考起來,似乎有點復雜,我們先想循環結束應當是什么?當然是顯示結果:
#顯示結果
print("64個格子,總的麥粒數量為:",c)
4.雖然循環比較復雜,但就剩這一部分了,也不得不開始考慮。首先考慮循環的進入和退出條件。循環是從第一個格子,循環到第64個格子,因為包含第64個格子本身,所以循環條件肯定是<=64,如果是<64,那循環到第63個格子就結束了:
while i<=64:
5.循環的邊界條件定義好了,現在考慮真正的計算,也就是循環體的部分:
c += x #總數 累計上 這一個格子的麥粒數
i += 1 #下一個格子
x = x*2 #下一個格子的麥粒數是這一個格子的2倍
其實總共就是這樣三行不能再少的運算。注意這里我們都使用了變量自身賦值的簡寫形式。
還有一點就是我們前面的例子都是把i = i+1放到循環體最后,其實這並不是必須的,只要在循環體中修改了循環條件相關的變量,不會導致死循環就可以。在哪里為循環變量賦值,是程序人員根據方便程度決定的。
好了,完整的貼一遍程序:
"""
國際象棋有8行8列共64格,
第1個格子放1粒麥子,第2個格子放2粒麥子,
以后每格都比前面格子數量多一倍,
求最終麥子總數。
"""
#定義一個變量來保存總的麥子數量,開始為0
c=0
#定義一個變量,循環1-64,來代表每一個格子
i=1
#假設每個格子中的麥子數量為x,初始也是1
x=1
#循環
while i<=64:
c += x #總數累計上這一個格子的麥粒數
i += 1 #下一個格子
x = x*2 #下一個格子的麥粒數是這一個格子的2倍
#顯示結果
print("64個格子,總的麥粒數量為:",c)
執行的結果一定要自己把程序輸入進去之后,自己看一看。
練習時間
練習1:由用戶輸入一個整數n,用while循環求整數1直至n的和。(提示,上一講介紹過函數input())
練習2:請將練習1的程序函數化,要求求和部分單獨為一個函數。
練習3:請將棋盤麥粒問題函數化,以便求出1至指定格子的麥粒數量總和。因為過大的數字會超出Python的計算范圍,我們假定允許用戶輸入的格子為1-64。
本講小結
- 計算機適合做枯燥、重復、大量的工作,循環在這種情況下起着重要的作用。while循環是較為自由的一種循環方式,用途很廣泛
- 循環的初始值和邊界條件非常重要,讓計算機執行正確,自己需要先設想自己處於計算機的位置上,想清楚
- 循環的邊界條件必須是可以變化的,需要循環的時候能循環,需要退出循環的時候要能變化條件,所以只能是變量
- 判斷邊界條件,需要使用“比較運算符”
- 比較運算符返回的是布爾值:True(真)、False(假),因為有了布爾值,計算機才能區別於計算器。特別注意區別比較運算的相等符號和賦值命令的等號
參考答案
中間的判斷表達式及其結果:
| 邏輯判斷表達式 | 結果 |
|---|---|
| 1 < 2 | True |
| 1 > 2 | False |
| 2.2 != 2.1 | True |
| "a" > "b" | False |
| "bcd" < "bd" | True |
| a="hello" | 賦值表達式,不能當做邏輯條件使用 |
| a == "hello" | True |
| 2 = 2 | 錯誤:2是字符,不是變量,不能被賦值 |
最后的三個練習請參考源碼ex1.py/ex2.py/ex3.py
