天天看點

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

本節書摘來自華章計算機《從問題到程式:用python學程式設計和計算》一書中的第2章,第2.11節,作者:裘宗燕 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

本書各章的主要内容将圍繞着怎樣通過程式設計解決計算問題展開,正文中對python語言的機制隻做必要的說明,有些細節情況沒有涉及。另外,用python程式設計也有許多有趣而且有用的技術。如果在各章的主要部分詳細羅列,也可能沖淡讨論的主線。但是上述兩方面的一些情況也值得介紹。本書采用的方法是在一些章的最後增加稱為“補充材料”一節,補充一些細節,供讀者參考,也供用本書教授課程的教師選用。

除了讨論語言細節和程式設計技術的兩個小節外,有時還總結了一些常用的程式設計模式。練習中的第1題總是對本章内容做些總結,其中列出本章讨論過的重要概念。

2.11.1 語言細節

這裡介紹python語言中的一些與本章内容有關的情況,作為本章内容的補充。這裡介紹的情況在本書中并不使用,對于初學者并不重要,可以有選擇地閱讀。提供這部分内容有兩方面意義,首先是使本書中對python語言的介紹較為完整。另一方面,讀者在進一步使用python去解決更複雜的問題時,也可能需要用到這裡介紹的功能。

整數的二進制、八進制和十六進制字面量形式

python隻有一種整數,即是類型為int的整數。但另一方面,這個語言卻為整數提供了多種不同的字面量描述方式。前面介紹的十進制字面量形式使用最普遍,但python還提供了二進制、八進制和十六進制的整數字面量形式。現在介紹有關情況。

首先,二進制、八進制和十六進制的整數字面量形式,都由一個引導部分和一個數值部分組成,一個整數仍然寫成一個連續的字元序列。這三種字面量的引導部分都以數字0開頭,在0之後用一個字元互相區分,而後緊接着是一串表示其數值的數字(對于十六進制,其中還能包含一些英文字母,見下)。

二進制整數字面量的引導部分是0b或者0b,随後表示數值的部分中隻能用兩個數字0和1,是一段0/1序列。例如0b110101和0b110100。在b或b之後立刻出現幾個0也允許,不影響結果的數值。二進制數的數值按如下方式計算:

這裡實際上假設寫出的二進制字面量包含n + 1位二進制數字。很容易看到,python的二進制字面量隻是整數的一種寫法,例如:

在二進制字面量裡出現超出0和1的數字将作為出錯。此外,python标準函數bin傳回與其整數參數對應的二進制字面量形式(結果是一個字元串)。例如:

八進制整數字面量的引導部分用0o或者0o(這兩種形式都很糟糕,0和o太像了),随後可以寫出任意長的數字0到7的序列。其計值公式是:

例如:

與二進制類似,表示數字的部分中不能出現數字8或9。标準函數oct傳回與其整數參數對應的八進制字面量形式(是一個字元串)。

十六進制整數字面量的引導部分用0x或者0x。這裡有一點麻煩:表示十六進制數的數值需要16個數字,但阿拉伯數字隻有10個。python采用計算機領域的習慣做法,用最前面6個英文字母填補空缺:a或a表示10,b或b表示11,c或c表示12,d或d表示13,e或e表示14,f或f表示15。下面是兩個十六進制字面量:

十六進制數表示的計值公式與上面類似:

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

标準函數hex傳回其整數參數對應的十六進制字面量字元串。

用二進制、八進制、十六進制字面量都可以描述任意大的整數。

整數的位運算

計算機裡的資料都用二進制編碼的形式表示,位(即二進制位,bit)是最基本的編碼機關,也是最小的資料表示機關。在實際問題中,有些對象的變化情況很簡單,隻用一個或幾個位就能表示,如果程式裡這種資料很多,把它們表示為某類型的對象,可能造成很大的存儲浪費。一種可能辦法是把多個這類資料對象存入一個整數類型的對象。此外,一些與底層有關的程式可能需要操作二進制位資料。例如,硬體工作狀态資訊通常用二進制位串表示,操作硬體時就需要用位串形式發指令。為适應這些情況,python語言提供了針對整數中二進制位的操作。有關的運算符稱為按位運算符。

先介紹基本的位運算,它們是按位運算符的基礎。一個二進制位隻能取值0或1,位運算就是對二進制位的運算,從一個或兩個0/1值出發,算出0/1結果。常用位運算共有四個,其中“否定”為一進制運算,其他都是二進制運算,運算規則很簡單,見下表:

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

https://yqfile.alicdn.com/b4d60ad3edea53ae54b601304c5f56f20d440ce0.png" >

從這個表可以看出,否定就是1變0而0變1;隻有1和1“與”的結果是1,其他情況的結果均為0;0和0“或”的結果為0,其他情況結果都是1;“異或”運算看兩個被操作位是否相同,相同時是1,否則是0。

與上面的位運算相對應,python語言定義了四個按位運算符,把它們作用于整型對象,得到整型結果。這些按位運算符把整數看成二進制位的序列。按位否定運算符是一進制運算符,作用于一個整數類型的運算對象,其餘三個都是二進制運算符,作用于兩個整數類型的運算對象。在運算時,它們對運算對象的一個個(或一對對)二進制位分别做位運算,得到結果的各位。四個按位運算符是:

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

看一個例子,假設變量x和y都是16位整數,它們值分别是:

對x和y做各種按位運算,得到的結果如下:

請讀者根據這個例子,對照上面說明,設法弄清各按位運算符的意義。注意,這裡整數表示中的逗号隻是為了閱讀友善。

這些運算符可以作用于任意大的整數對象,得到任意大的結果。如果兩個整數的長度不同,python有預設的處理方式,總之不會丢失資訊。

除了上面4個按位運算符外,python還有兩個與二進制位有關的整數運算符,分别是左移運算符“<<”和右移運算符“>>”。它們的左邊是被位移的整數對象,右邊的整數表示希望左移或右移的位數。舉例說,對上面的y将有:

可以看到,左移(或右移)空出的二進制位全部補0。

另請注意,上面的這六個運算符都是做運算,産生新的整數對象。例如3 << 3“算出”一個結果(一個新的整數對象24)。假設變量z的值是3,z << 3得到24,但z的值不變。要改變z的值就需要指派,或者用下面介紹的擴充指派運算符。

上述運算符的優先級情況有些複雜:一進制否定運算符的優先級與一進制正負号相同,移位運算符的優先級低于整數加減,三個二進制按位運算符的優先級互不相同,從高到低依次為 &、|、^,但都低于移位運算符。所有二進制運算符都按從左到右結合。例如x << 3 >> 5相當于(x << 3) >> 5。由于優先級的情況比較複雜,建議少寫過分複雜的表達式,多用括号顯式描述運算的順序。

與上述5個二進制位運算符相對應,python有5個擴充指派運算符,它們分别基于指派符之後的第二個參數修改左邊表達式的對象(最簡單情況用變量表示):

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

例如,x <<= 5效果相當于x = x << 5,但書寫友善,通常效率更高。

有關浮點數的進一步說明

進階語言的浮點數一般都直接映射到語言系統運作所在的計算機硬體,是以進階語言中的浮點數及浮點數計算直接反映了硬體的相關特征。在計算機設計層面,目前大多數硬體的浮點數計算都采用ieee 754标準。這裡的ieee是電氣和電子工程師協會的簡寫,它是目前全球最大的一個非營利性專業技術學會。ieee 754是ieee頒布的一個浮點數算術系統标準,被大多數計算機硬體廠商采納。python語言并沒有強制性地規定采用ieee 754浮點數(如果那樣,在不執行這個标準的硬體上将很難實作python),但常見環境中運作的python中的浮點數應該符合這個标準。下面介紹最常用的浮點數情況。

在正常系統上運作的cpython都采用ieee 754的雙精度浮點數标準作為語言的浮點數。這種浮點數用64位二進制碼表示,其中指數部分用11位,可以表示-1023到1023;表示數值的部分(術語是尾數部分)用52位,另有一個符号位表示正負數。

指數部分的大小決定了浮點數的表示範圍,絕對值最大(最小)大約是±21023的量級。換算到十進制數,表示的範圍(如前面所說)大緻為±5×10-324~1.7×10308。尾數的位數決定了浮點數的表示精度,52位二進制數大約相當于十進制的16到17位。超過上述範圍的實數在這裡無法表示。即使在範圍内,浮點數表示也受到精度的限制,隻能表示數軸上一個個能用二進制編碼形式表示的孤立點。

ieee 754的具體表示方式還有些細節,但知道上面這些對于初學者已經夠了。從基本程式設計的需要看,隻需了解這種浮點數标準的表示範圍和精度。許多其他語言的實作也采用ieee 754的雙精度浮點,其他python實作多半也采用這個标準。

浮點數舍入轉換

從浮點數轉換到整數,預設轉換方式是舍去小數部分,通常稱為截尾。内置函數round采用另一種轉換方式,通過舍入得到與浮點數最近的整數。但什麼是最近呢?中國小的算術裡教過“四舍五入”的舍入規則,但顯然這一規則偏向于入,從統計的觀點看,這樣得到的整數值偏大。銀行總按四舍五入付錢就會虧本,長期做下去累積的“入”也會導緻虧本很多。為了防止這種情況,人們提出了另一種更為公平的舍入方式。

python的round函數采用所謂“銀行家舍入”方法,可稱為“四舍六入五取偶”舍入,這也是目前常見計算機硬體采用的舍入計算标準(ieee 754浮點數标準中的舍入計算标準方法,目前程式設計語言和工具大都直接借用這一舍入計算标準)。

具體說,如果需要舍入部分的最高位小于等于4和大于等于6,就直接分别舍去或者進位。假設需要舍入的那段數字的最高位是5,如果在這個5之後還有不為0的有效位(也就是說,明确地大于5),轉換時就進位。如果5之後都是0位,則根據5的前一位舍入:前一位是奇數時進位,前一位是偶數時舍去。這裡把0也看作偶數。與簡單的四舍五入,“銀行家舍入”在機率上更公平。按照這種規則,可以看到:

字元串和換意序列

在寫字元串時有些字元無法直接寫出,換行符是這種字元的典型代表。由于有這種情況,python(與其他一些語言一樣)引入了換意序列的概念,用一種特殊形式的字元序列(包含兩個或更多字元)表示字元串裡的一個字元。換意序列的第一個字元總是下劃線符,也就是說,字元串裡出現的下劃線符總表示換意序列開始,後面字元(序列)決定換意序清單示的字元。前面介紹過幾個常用換意字元,下面是它們和另外幾個及其解釋:

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

https://yqfile.alicdn.com/5c72d4ef1ea6f97402950d6b9deccf400ed36762.png" >

《從問題到程式:用Python學程式設計和計算》——2.11 補充材料

https://yqfile.alicdn.com/5d5b481a5425752e24065763779f2851cfcda1e8.png" >

其中,“換行符”表示反斜線後緊跟換行。“ooo”表示反斜線後緊跟3位八進制數字,這種形式的換意序清單示編碼為八進制為ooo的字元。“xhh”表示反斜線後有一個x,後跟2位十六進制數字(包括a/b/c/d/e/f),這種換意序清單示編碼為hh的字元。

除上面這些表示形式外,python還支援用于寫出所有unicode字元的換意序列形式。這方面的細節這裡不進一步介紹了。

用一對三個引号的形式,在字元串字面量中可以包含換行,不必再寫換意序列“n”。此外,采用三個引号的形式,多數時候也不需要用單引号和雙引号的換意序列“'”和“"”。但有時還可能需要,例如下面字面量沒問題:

開頭的連續四個引号被正确解析為一個連續三引号和一個雙引号。但

将報錯。開始的連續四個雙引号可以正确解析,但最後的四個雙引号不行:解釋器看到前三個雙引号,認為它是字元串的結束标志,字元串到此結束。又看到一個雙引号,解釋器認為另一個字元串從這裡開始。遇到換行,它認為字元串沒完,報文法錯誤。要正确寫出這個字元串(内容是一對雙引号括起的一句話),可以寫:

或者更簡單的,用一對三個單引号作為字元串括号。

如果在一行裡連續寫出幾個字元串,解釋器會自動将其連接配接成一個字元串:

但是,解釋器隻是在處理字元串字面量,才這樣做。例如:

如果要拼接作為變量值的字元串,必須用拼接運算符(加法符号)。

基本語句

表達式可以寫在程式中任何應該寫語句的位置,這樣的表達式就構成了一個表達式語句。在一般程式裡,把普通表達式(例如算術表達式1 + 2等)作為語句使用意義不大。運作中執行這個語句時就求值該表達式,求出值後該語句的執行完成,表達式的值随後将被丢掉,不産生任何效果(除了表達式計算可能耗費計算機時間)。

有用的表達式語句主要是函數調用。在python裡,函數調用是一類基本表達式。獨立寫出的函數調用就構成了一個表達式語句。前面程式裡已經多次出現這種表達式語句,例如對内置函數print的調用語句。實際上,任何無特定傳回值的函數(實際上傳回none),通常都以表達式語句的方式使用。

python允許在一行中寫多個語句,這時要求語句之間加分号。例如:

這樣一行仍看作一個語句,在其執行時将順序執行其中的成分語句。最後的成分語句執行完畢時整個語句的執行完成。這種語句是順序語句的一種形式。當然,這種形式中也可以寫任何語句,不僅是指派語句。例如在一個循環裡寫:

雖然語言允許上面的形式,有時也會看到有人把幾個簡單語句寫在一行。但在python程式設計實踐中,人們不大倡導這種形式。在絕大多數python程式裡,人們堅持一行一個語句的基本規則。在一些情況下用并行指派語句同時給幾個變量指派。

2.11.2 程式設計技術

本節讨論幾個與程式設計有關的技術問題。

條件語句與條件表達式

在一些情況下,條件語句和條件表達式都能使用。例如下面同樣函數的兩個定義:

兩個函數功能完全一樣,但後一個簡單許多。參考這個執行個體,可以總結出适合使用條件表達式的情況:在需要根據條件,從兩種不同的表達式計算中選一個,而且計算比較簡單時,采用條件表達式可以簡化程式。條件語句适合用于各種指派情況,其一個分支中可以包含任意複雜的操作序列。用來處理上面問題,是大材小用了。

在後面章節裡,還會看到許多使用條件表達式的有意思的例子。

寫表達式的技術

如果需要寫的計算表達式非常複雜,應該設法做出安排,使寫出的程式代碼清晰易讀,容易檢查表達式書寫的正确性。有兩種方法可以參考。

其一是分解表達式,用一個或幾個中間變量記錄表達式中子部分的結果,而後用這些變量的值組合出最終的表達式。适當的分解有助于保證表達式的可讀性和正确性。

如果确實需要或者希望寫很長的表達式,那就需要安排好表達式的多行格式。首先做好準備,為能把表達式寫在多個行裡,可以參考前面基于三邊求三角形函數中條件的寫法,先加入一個括号保證解釋器不會把表達式截斷,而後在每行适當的地方斷行,各行中屬于同層的結構互相對齊(仿照python語言的格式)。例如,下面是一個長表達式:

為能寫跨越多行的長表達式,在上面表達式開始加了一個括号。在上述表達式中,我們把屬于同一層的子表達式對齊,分别放在幾行。這樣寫表達式,很容易檢查,容易發現錯誤。idle或其他支援python的開發系統都能幫助維持良好的表達式形式。但如何斷行等,還是需要人做好安排。

數值計算函數中錯誤情況的傳回值

有些數值計算函數不是全函數(相對于參數的類型而言),如果被調用時得到的參數不滿足需要,它将無法傳回一個正确的結果。在這種情況下可以考慮報錯(後面介紹),也可以考慮讓函數傳回一個特殊值。不同的處理方式各有優缺點。

本章中出現了幾種處理方法:求階乘函數對于負的參數都傳回0;計算三角形面積的函數,對于不能構成三角形三邊的參數,傳回float("nan")表達式的值。後面還會看到一些情況,這些做法都可以參考。

本章介紹的python關鍵字

這裡列出本章讨論過的python關鍵字,供讀者參考:from,import,and,or,not,true,false,if,else,elif,for,in,while,break,continue,def,return,none,pass。在總共33個關鍵字中,本章已經介紹了19個。包括:

3個特殊字面量,表示邏輯常量的true和false,以及特殊值none;

3個邏輯運算符and、or和not(not還有其他使用方式,見後面的介紹);

流程控制結構中用6個,if,else,elif、for、in、while;

專門語句用4個:break,continue,return,pass;

程式包導入語句用from和import;

函數定義def。