天天看點

《Python 3程式開發指南(第2版•修訂版)》——2.5 執行個體

本節書摘來自異步社群《python 3程式開發指南(第2版•修訂版)》一書中的第2章,第2.5節,作者[英]mark summerfield,王弘博,孫傳慶 譯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

在這一節中,我們将根據本章以及前面一章中所學的知識,提供兩個雖小但完整的程式,以助于鞏固到此為止所學的python知識。第一個程式有點偏數學化,但是非常小,大約35行代碼。第二個程式是關于文本處理的,并且更具體,其中包含7個函數,大約80行代碼。

二次方程是指形如ax2 + bx + c = 0的方程,其中,a不為0描述的是抛物線。這一方程的根可以由公式

《Python 3程式開發指南(第2版•修訂版)》——2.5 執行個體

得出,其中,公式的b2-4ac部分稱為判别式——如果為正值,那麼該方程有兩個實根;如果為0,那麼該方程有一個實根;如果為負值,就有兩個複數根。我們将編寫一個程式,該程式接受使用者輸入的a、b、c值(b與c均可為0),之後計算并輸出方程的根1。

首先我們看一個運作的執行個體,之後将講解其代碼。

對于系數1.5、-3、6,其輸出(有些數字經過處理)為:

上面的輸出并不能滿足我們的要求——比如,我們不希望使用+ -3.0x,而更希望直接使用- 3.0x,對于系數為0的情況,則不希望方程中還顯示其對應的項。在練習中,你将有機會完善本程式中存在的這些不足。

現在我們開始閱讀和講解程式代碼,代碼是從3個導入語句開始的:

由于用于實數與複數的平方根函數是不同的,是以,浮點數學庫與複數數學庫都需要導入。由于需要使用sys.float_info.epsilon将浮點數與0進行比較,是以我們還要導入sys庫。

我們還需要一個從使用者處擷取浮點數的函數:

這一函數将進行循環,直至使用者輸入一個有效的浮點數(比如0.5、-9、21、4.92等)。如果allow_zero為true,那麼也可以接受0。

定義了get_float()函數後,代碼的其餘部分将得以執行,我們将分3個部分講解這部分代碼,從使用者互動部分開始。

由于定義了get_float()函數,使得擷取方程系數a、b、c變得很簡單。布爾型的第2個參數用于确定是否可以接受0。

上面的代碼看起來與公式似乎有所不同,這是因為我們首先從計算判别式開始。如果判别式為0,就會知道該方程隻有一個實數解,是以可以直接計算;否則,我們可以先計算判别式的實數平方根或複數平方根,并進而計算出方程的根。

由于對這一執行個體而言,python對浮點數的預設支援已足夠,是以我們沒有進行任何其他格式化,但是對兩個特殊的字元,我們使用了unicode字元名。

使用位置參數(用其索引位置作為字段名)的更健壯的替代方式是使用locals()傳回的字典,這也是本章前面看到過的一種技術。

并且,如果使用的是python 3.1,我們可以忽略字段名,而由python使用傳遞給str.format()的位置參數生成字段。

這是便利的,但并不像使用命名參數那樣健壯,在需要使用格式規約時也沒那麼豐富多變。盡管如此,對很多簡單的情況,這種文法既是容易的,也是有用的。

一個常見的需求是:擷取一個資料集,并将其使用html呈現。在這一小節中,我們将開發一個程式,該程式讀入一個檔案,該檔案使用的是簡單的csv(逗号分隔值)格式,輸出時則使用html表格,其中包含該檔案的資料。python本身帶有一個功能強大而複雜的csv子產品,可用于處理csv格式與類似的資料格式——但這裡我們将自己編寫所有代碼。

csv格式每行一個記錄,每個記錄使用逗号分隔為多個字段。每個字段可以是字元串,也可以是數字。字元串必須使用單引号或雙引号包含起來,數字不應該使用引号包含,除非其中包含逗号。在字元串内部使用逗号是允許的,但不能充當字段分隔符。我們假定第一條記錄包含字段labels。我們将要産生的輸出是html表格,其中的文本采用左對齊方式(在html中是預設的),數字則采用右對齊方式,每個記錄一列,每個字段一個單元。

該程式必須可以輸出html表格的開标簽,之後讀取每行資料,對每行資料,輸出其對應的html列,并在末尾輸出html表格的閉标簽。對背景色,要求第一列(該列用于顯示字段标号)為淺綠,資料列的背景色則在白色與淺黃色之間變換。要注意的是,我們必須確定特殊的html字元(“&”、“<”與“>”)必須經過正确的轉義處理,并希望字元串經過适當處理。

下面給出的是一段樣本資料:

假定樣本資料存放在檔案data/co2-sample.csv中,并使用指令csv2html.py < data/ co2-sample.csv > co2-sample.html,則檔案co2-sample.html包含的内容類似于如下格式:

我們對輸出進行了稍許處理,并忽略了某些行(使用省略号表示)。我們使用了一種非常簡單的html到html 4之間的過渡格式,并且沒有使用類型表,圖2-7展示了在web浏覽器中輸出的情況。

《Python 3程式開發指南(第2版•修訂版)》——2.5 執行個體

在了解了程式如何使用以及其功能之後,我們開始查閱程式的實作代碼。該程式從導入sys子產品開始,我們沒有展示該行代碼,也沒有展示其他導入語句,除非是不同尋常的或授權的讨論。程式的最後一個語句是一個函數調用:

雖然python不像其他語言那樣需要入口點,但是在python程式中,建立一個稱為main()的函數,并通過對該函數的調用來開始程式的實際處理流程也是非常常見的。由于沒有哪一個函數可以在建立之前就被調用,是以我們必須確定在其依賴的函數建立之後再調用main()。函數在檔案中出現的順序(即函數的建立順序)則無關緊要。

在csv2html.py程式中,我們調用的第一個函數是main(),其中依次調用了print_start()與print_line(),print_line()則調用了extract_ fields()與escape_html(),圖2-8中展示了使用的程式結構。

《Python 3程式開發指南(第2版•修訂版)》——2.5 執行個體

python讀入檔案時,從頂部開始執行,這一執行個體也是如此,首先執行的是導入語句,之後建立了main()函數,再之後建立了其他函數,其順序與檔案中出現的順序一緻。在檔案尾部調用main()函數時,main()函數要調用的所有函數(以及這些函數要調用的函數)都已經存在。執行過程與我們通常認為的一樣,從對main()的調用開始。

我們将依次檢視每個函數,從main()開始:

maxwidth變量用于限制每個cell中的字元數——如果某個字段大于這個值,我們将對其削減,并通過添加省略号來表明這一點。我們下面就開始檢視print_start()、print_line()以及print_end()等函數,while循環對每行輸入進行疊代處理——輸入可以來自使用者的鍵盤輸入,但是我們更希望來自重定向檔案。我們設定了想要使用的顔色,并調用print_line()将該行以html表格列的形式輸出。

我們也可以不建立這兩個函數,而隻是将相關的print()函數調用放置在main()函數中。但是我們更願意将這些功能邏輯分離出來,因為這會使程式更加靈活(即便在較小的程式中這并不重要)。

要注意的是,我們不能使用str.split(",")将每行分隔成不同的字段,因為在引号包含的字元串内也可能包含逗号。是以,我們将這一功能實作在extract_fields()函數中。對字段清單(作為字元串,但沒有包圍的引号),我們在其上進行疊代,并為每個字段建立一個表格單元。

如果某字段為空,就輸出一個空cell。如果某個字段使用引号進行包含,那麼可能是一個字元串,也可能是一個數字,使用引号包含數字的目的是允許數字内部使用逗号,比如,“1 566”。為此,我們生成字段的一個副本将其内部的逗号移除,并嘗試将其轉換為一個浮點數。如果轉換成功,就輸出一個右對齊的單元,其中的字段四舍五入為最近的一個整數,并以整數的形式輸出;如果轉換失敗,就以字元串形式輸出該字段。這裡,我們使用str.title()來整理字母的大小寫,并使用and替換and,以便糾正str.title()對其進行的不必要的更改。我們之後對任意特殊的html字元進行轉義,或者列印其完整的字段,或者列印其maxwidth個字元,并加上省略号。一種更簡單的使用内部替換字段的替代方案是使用字元串分片。這種方法的另一個優勢是需要較少的輸入操作。

該函數逐個字元讀入給定的行,累積成一個字段清單——每個字段都是一個不帶引号包含的字元串。該函數可以處理不帶引号的字段,也可以處理使用單引号或雙引号的字段,并正确處理逗号與引号(雙引号字元串中的單引号,單引号字元串中的雙引号)。

這一函數直截了當地使用适當的html實體替換每個特殊的html字元。我們當然必須首先替換&符号(盡管對尖括号而言,順序并不重要)。python的标準庫包含此函數的一個稍複雜的版本——在下一個練習中,你将有機會使用這一函數,并在第7章中再一次了解這一函數。