本節書摘來自華章計算機《從問題到程式:用python學程式設計和計算》一書中的第2章,第2.1節,作者 裘宗燕,更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。
寫程式是為了實作所需要的計算,計算中總需要處理資料,是以寫程式時必然要涉及資料的描述,以及從資料出發的計算過程描述。
人們在學數學時已經寫過許多數學表達式,其中的一些表達式描述的就是從一些數值出發的計算過程,例如下面這個數學表達式:
這一表達式描述的是從一些數值出發,通過三角函數和各種算術運算,要求算出一個結果。複雜的計算需要用計算機完成,是以在用python(或其他語言)寫程式時,也經常需要寫這種計算描述。上面數學表達式對應的python語言描述是:
<code>5.17 + 1.7 * (2.36 + sin(0.37)) / (12.3**2 – cos(15.4))</code>
這樣一段描述稱為一個python表達式,是線性的字元序列。與數學表達式相比,它多了一個作用:可以指揮計算機完成計算,算出一個結果。
學習用python程式設計式,第一步是學習表達式的描述方式,弄清如何正确寫出所需要的表達式。為此需要了解基本資料和表達式的寫法規定:可以包含哪些成分?各種成分的意義是什麼?怎樣組合?表示怎樣的計算過程?本章首先介紹這方面情況。
python的基本程式機關是指令(或稱語句),表達式是指令的組成部分。最基本的指令稱為指派,要求把表達式算出的值儲存起來供後面所用。還有另外一些基本指令,後面将逐漸介紹。任何複雜一點的計算都需要通過許多指令才能完成,而且需要根據實際情況,控制這些指令的執行過程。python為此提供了一套控制結構。進一步說,随着程式變得更複雜,平鋪直叙的程式将越來越難寫好。為此python引入了一種稱為函數的基本抽象機制,使我們可以把一段計算描述包裝成一個整體,抽象為一個概念。
本章首先介紹基本的資料描述方式,說明如何描述簡單的計算過程。讀者将接觸到計算領域的許多重要概念,看到它們的地位和作用。而後介紹語句和語句的執行控制,随後介紹函數的概念。讨論中給出了許多程式示例,供讀者參考。
要了解python的基本計算功能,最好的開始方式就是看一些簡單計算執行個體。
讀者在第1章已經看到過最簡單的python程式。前面說過,在啟動了idle的程式執行視窗,或者python的指令行視窗之後,就會看到提示符“>>>”。我們的輸入将出現在它後面。假定輸入1之後按壓enter鍵,就能看到解釋器完成的計算:
注意,解釋器并不是把前一行裡人的輸入直接拷貝到下面一行,而是完成了第1章說過的三步工作:讀入python程式代碼單元(這裡單元的内容就是一個字元1),完成程式要求的計算(這裡要求解釋器計算出1的值,得到結果還是1),最後,解釋器在随後的行中顯示計算結果。換句話說,這裡輸入的表達式1就是要求解釋器工作的指令,解釋器執行這個指令的效果是把計算得到的結果1顯示出來。下面的指令能更好地表現解釋器實作的計算過程,這裡送給解釋器的是“1 + 2”,解釋器給出計算結果是3。
可見,解釋器的工作确實不是簡單拷貝。
在上面兩個例子裡,我們送給解釋器由一個或幾個字元構成的序列,它們就是最簡單的python程式(結構),稱為表達式。表達式也是一種指令,它要求解釋器完成一個計算,得到的計算結果稱為該表達式的值。在互動式執行方式下,解釋器将計算出表達式的值,然後把這個值輸出在視窗裡,如上所示。解釋器計算表達式的操作也常說成是對表達式求值,或者求值該表達式。為了更清楚,在表示互動式計算的情況時,我們将一直用正體表示人的輸入(出現在提示符之後),用斜體表示解釋器給出的計算結果。
并不是任何數字序列都是合法的python程式單元,例如:
這裡用粗體表示解釋器給出的錯誤資訊,idle實際上用特殊顔色輸出,說明計算中出現了錯誤。這裡的錯誤是文法錯誤(syntaxerror),冒号後的細節解釋說“01不是形式合法單詞(token)”。這個例子說明python對整數的形式有規定,一般而言,python對具體單詞(單詞類别)都有明确的書寫形式要求,初學者首先要關注這方面的情況。
我們通過鍵盤輸入一串字元(包括空格等),python解釋器将把它切分成一系列的基本文法成分,稱為單詞(token)。例如“1 + 2”包含三個單詞“1”、“+”和“2”。了解這個基本情況,有助于了解python系統對程式的處理。
python能處理的基本資料類别之一是整數。整數常用十進制數字的序清單示,但除非是0,否則數字序列的第一個數字不能是0(上面出錯的輸入,原因就在這裡)。
在python裡可以寫任意長(任意大)的整數,例如:
計算機硬體隻能處理固定長度的整數。例如,在目前常見的64位計算機裡,硬體指令一般能處理用64位二進制編碼表示的整數,其範圍為-263~263-1,也就是-9223372036854775808到9223372036854775807。在python系統裡對整數沒有數值限制,是通過複雜的軟體技術實作的。當然,由于計算機都是規模有窮的實體裝置,其資源有限,無論采用什麼樣的軟體技術,也隻能在計算機硬體的實體限制内工作。實際python系統能表達的整數還是有窮的,但這個範圍非常大,一般的使用都不會超出其實際限制。但在另一方面,計算中涉及的整數越大,計算也會越慢。讀者可以通過試驗看到這種情況。
整數的基本計算用算術運算符描述,下面6個算術運算符可用于整數:
其中的加号既作為二進制加法運算符,也作為正号;減号既作為二進制減法運算符,也作為負号。其餘幾個都是二進制運算符。這裡有兩個除法運算符,其中 // 表示整除,得到商的整數部分,對兩個整數應用 / 得到的不是整數,而是下節将要介紹的浮點數。% 運算符求整除的餘數,** 求整數的乘幂。從下面幾個例子中可以看到一些情況:
第一個表達式的結果是帶小數點的數,這就是所謂的浮點數。從上面例子還可以看到負數的整除和求餘數規則,無論正整數或負整數,解釋器都保證有:
被除數=除數×商+餘數
整數運算可能得到很大的結果,例如:
結果是一個超過300位的整數,輸出了多行。這是準确的結果。
在python語言裡寫算術表達式時,人們提倡在一般運算符的前後各加一個空格,但在正負号和後面數字之間不留白格,乘方運算符前後也不留白格。當然,這裡留不留白格,表達式的語義不變,提倡留白格的形式,隻是為了使寫出的表達式更清晰。
python允許寫任意複雜的算術表達式,隻要其形式符合要求,運算符正确應用于運算對象。表達式裡出現多個運算符,就有計算的順序問題。這裡的規則與數學中類似:先乘方,再乘除(包括求餘數),最後加減,也允許用括号規定計算順序。例如:
底數前面的正負号作用在乘方之後,其他情況下正負号先起作用。如:
多個乘除或多個加減運算符在表達式中順序出現時,總是從左到右順序計算。而多個乘幂運算符順序出現,則先做右邊的乘幂。例如:
第2個例子求的是2的9次幂,而不是8的二次方。
基于多個二進制運算符,通過組合可以寫出任意複雜的表達式。在這種情況下,python語言必須做出嚴格的規定,保證每個合法表達式的計算過程都有嚴格定義的唯一順序。這種規定一般包括幾個方面:
1)把運算符分類,給每個類别的運算符規定一個優先級。屬于不同類别的運算符在表達式裡順序出現時,優先級高的運算符先做運算。在python語言裡,乘幂運算符的優先級高于一進制運算符(隻有一個運算對象)正号和負号,正負号的優先級高于乘除,乘除運算符的優先級高于加減。
2)給每類運算符規定一種結合順序,從左到右或從右到左。當屬于同一優先級的運算符在表達式裡順序出現時,就按其結合順序決定計算的順序。如前所述,在python裡加減運算符和乘除運算符都采用從左到右的結合順序,乘幂運算符則采用從右到左的結合順序。一進制運算符隻有一個運算對象,也采用從右到左的結合順序。例如,在python裡可以寫 -------20,雖然這樣寫的意義不大,但也是合法表達式。
3)提供括号,用于明确描述特定計算順序。注意,一般的程式語言都隻用圓括号作為描述運算順序的括号,其他括号另有它用。python也是這樣。
4)如果一個運算符有多個運算對象,可能需要規定幾個運算對象的計算順序。python規定多個運算對象從左到右順序地一個個計算,例如,對于表達式(3 + 5) * (7 – 2),先算出(3 + 5),再算出(7 – 2),最後使用乘法。
最後一條有點奇怪。對于從基本資料(如整數)出發寫出的算術表達式,先計算運算符的哪個運算對象,得到的結果都一樣。但是,确實存在另外一些python表達式,它們的情況可能有所不同。在後面章節裡可以看到這種規定的意義。
前面說過,python解釋器首先把程式看作是一系列單詞。在前面程式執行個體中,我們已經看到了一些不同的單詞,如:整數、左右括号、算術運算符。其中有兩個算術運算符的形式比較特殊,整除運算符由連續的兩個斜線符構成,乘幂運算符由兩個連續的星号構成。這種運算符稱為組合運算符,它們的兩個字元之間不能有空格。
整數也很特殊:滿足要求的一串數字構成一個整數,它本身又代表了計算中的一項資料,是一個量。具有這種性質的單詞稱為字面量,意指直接寫出的資料。
各種單詞都有嚴格規定的寫法,但是,即使一串單詞中每一個的形式都合法,整個單詞序列也未必構成一個合法的表達式。例如:
這裡出現了表達式(或其他python程式結構)構成方式的錯誤,稱為文法(syntax)錯誤。解釋器在處理程式的過程中發現文法錯,可以明确給出發現錯誤的位置。在idle裡,系統會用特殊的顔色标明這個位置。如第1章中的介紹,應該從這個位置出發檢查程式,找到錯誤的根源并修正之。上面第一個例子可能是忘記寫運算符,第二個例子可能是組合運算符的兩個字元之間多了空格。具體怎麼更正由寫程式的人确定。
文法錯誤是程式中的結構描述錯誤,說明程式中某個片段的寫法不符合python語言的要求。不符合python文法的字元序列不是python程式,是以不能執行。算術表達式是python語言中的一種結構,下面還要介紹python語言的其他結構。每種結構都有規定的文法形式,要在程式裡使用某種結構,就必須按正确的文法形式寫出它們。
還請注意,即使表達式的形式正确,計算中也可能出錯。例如:
最後一行是解釋器說明具體錯誤的資訊:zerodivisionerror意為計算中出現除以0的錯誤。倒數第二行給出了發生錯誤的表達式,前面兩行是定位資訊。這個錯誤屬于第1章說明的動态運作錯誤。動态錯誤還有很多,後面會看到。
python允許寫任意長的表達式,但原則上說一個表達式應該寫在一行裡。在互動式計算的環境中,一旦輸入換行符号(用enter鍵),解釋器就認為表達式結束了,就會去檢查它的形式是否合法。如果文法正确,解釋器就會求值這個表達式。
假設我們計劃輸入一個很長的表達式,但為了某種需要(例如,太長了看不清楚),希望在其中一些地方斷行。這時就遇到了問題:一旦輸入一個換行符号,python解釋器就試圖去求值所給的輸入。如果這行輸入恰好滿足表達式的文法,解釋器就會把它看作完整的表達式,求出它的值輸出。如果這行的内容不是一個正确的表達式,解釋器就輸出錯誤資訊。無論如何,這兩種情況可能都不是我們期望的。
為滿足人們的需要,python做出了特殊的規定。存在兩種情況,解釋器将認為目前表達式沒結束,其内容延續到下一行,将把下一行的内容作為本行的繼續:
1)在讀入一行遇到換行符時存在明顯的未完成結構。以算術表達式為例,如果一行中出現的左括号沒有比對的右括号,解釋器就繼續讀入下一行作為本行的續行。
2)一行最後一個字元(換行符前的那個字元)是反斜線符号 ,這時無論目前行的内容如何,解釋器都把下一行作為續行。需要注意,在反斜線符和換行符之間不能有任何字元,包括不能有空格。一行最後的反斜線符号也稱為續行符。
允許利用這兩條規則反複續行。了解了這些情況,我們就可以寫出任意長的表達式,可以根據自己的想法和需要,把一個複雜表達式寫在任意多個連續的行中。
python對于格式的上述要求(一個結構部分應該寫在一行裡)和續行規定(上面兩條規則)是普遍有效的,對于任何程式結構都有效。
python用浮點數模拟數學中的實數,通過浮點數計算模拟實數計算。當然,數學的實數一般不能表示為有窮長度的數字序列(例如是無理數),而python浮點數是有窮表示,是以隻能表示一定範圍裡的數,而且精度有限,隻能表示一些實數的近似值。
cpython(也就是說,python軟體基金會釋出的官方的語言解釋器)直接使用計算機硬體支援的标準浮點數。常見硬體都采用ieee754浮點數标準,标準浮點數具有16~17位十進制精度,表示範圍大緻為,絕對值太小的實數将歸結為0,絕對值更大的實數也無法表示。這些情況與前面介紹的整數不同。更詳細的情況,可參看本章最後“語言細節”一節的内容。
浮點數字面量用十進制數字的序清單示,但描述中必須或者包括一個表示小數點的圓點,或者包括一個指數部分。例如:
浮點數的基本部分表示其數值,這部分稱為尾數。字元e之後的部分稱為指數,表示數的量級。表示指數的字元可以用e或者e,随後是一個可以包含正負号的整數指數。幾個部分之間必須順序相連,不能有空格。上面最後的例子說明,過大的浮點數在python裡無法表示,系統并不認為出錯,而是給出inf表示這種情況(正無窮)。浮點數字面量,或者浮點數計算的結果,都可能超出可表示範圍,這種情況稱為浮點數溢出。
注意,對于同一個浮點數,可以用多種不同方式去描述它,例如,1234.0、1.234e3、0.1234e4是寫法不同的三個浮點數字面量,而它們描述的是同一個浮點數,并不因為在字面量寫法上的差異而有任何不同。
在顯示計算結果時,解釋器會自動選擇合适的方式,盡可能使輸出容易閱讀:
如果需要表示的實數的精度太高,python浮點數無法滿足需要(例如上面第二個例子,字面量包含20位有效數字),解釋器就會做适當的截斷和舍入。細心的讀者可能看到上面第二個浮點字面量的輸出,奇怪結果的最後一位為什麼是7而不是8。從本章最後的補充閱讀材料裡可以看到一些端倪。
對于浮點數,也可以做前面給出的各種算術運算,可以寫出任意複雜的計算表達式,描述時同樣要注意算術運算符的優先級、結合順序,也可以使用括号。例如:
比較這裡的最後兩個例子,從中可以看到了一個現象:浮點數的計算不準确,有誤差。對最後一個例子,正确無誤的結果是0.66,系統給出的卻是0.6600000000000001。浮點數計算有誤差是計算中必然出現的一種現象。下面是另一個例子:
在數學上,這裡計算的兩個表達式等價,但在python系統裡卻得到了不同結果。這一情況并不說明python系統有錯,而是計算機硬體浮點數表示和計算方式的必然結果:由于浮點數的精度有限,在反複計算中需要不斷舍入,累積的誤差可能會越來越大。采用不同計算方式,産生的累積誤差可能不同,是以造成上面的現象。
基于浮點數的計算也稱為數值計算,數值計算中會出現計算誤差,是以是一種近似計算,得到的結果是不精确的。最早開發計算機,就是為了解決複雜的科學與工程問題,其中常涉及非常複雜繁瑣的數值計算。在很長的計算過程中,誤差積累有可能變得非常大,甚至使最終得到的結果毫無意義。控制和評估數值計算中的誤差,是科學與工程計算中的重要問題,既是非常重要的理論研究課題,也具有重要的實際意義。
python也支援複數的表示和複數計算。在數學裡複數通常寫成,其中a和b均為實數,分别稱為這個複數的實部和虛部。python中的複數表示與此類似,但用字元j或者j作為虛部的符号,例如1 + 2j、1.2 + 2.7j。
複數用一對浮點數表示,兩個浮點數分别表示其實部和虛部。例如:
可以看到,解釋器輸出複數時總用一對括号将其括起。基本四則運算和乘方都能作用于複數,但整除和求餘數運算對複數無定義。