返璞歸真
許多流行的玩具都以這樣一個概念為基礎:簡單的積木。這些簡單的積木可通過多種方式組合在一起構造出全新的作品 ―― 有時甚至完全令人出乎意料。這一概念同樣適用于現實生活中的建筑領域,將基本原材料組合在一起,形成有用的建筑物。平凡無奇的材料、技術和工具簡化了新建筑物的建造過程,同樣也簡化了對新踏入此領域的人員的培訓。
相同的基本概念也適用于計算機程序開發技術,包括以 Python 編程語言編寫的程序。本文介紹了使用 Python 創建基本構件 (building block) 的方法,可用于解決更為復雜的問題。這些基本構件可能小而簡單,也可能龐大而復雜。無論采用哪種形式,我們這場游戲的目的就是定義基本構件,然后使用它們來創建專屬于您的杰作。
函數:封裝邏輯
在本系列的前幾篇文章中,您通常不得不重復輸入所有代碼,即便它與上一行代碼完全相同。此要求的惟一特例就是變量的使用:一旦初始化了變量的內容,之后就可以隨時重用。顯而易見,這一用法的普及對我們大有好處。
描述杰出程序員的最流行的箴言之一就是他們很懶惰。這并不表示杰出的程序員不努力工作 ―― 而是說他們喜歡靈活的工作方法,除非絕對必要,否則從不反復做任何相同的事情。這也就意味著在您需要編寫代碼之前,首先考慮如何實現重用。Python 中有多種可實現重用的途徑,但最簡單的技術莫過于使用函數,也稱為方法 或子例程。
與絕大多數現代編程語言類似,Python 支持使用方法將一組語句封裝在一起,從而可在必要時重復使用。清單 1 給出了一段簡單的偽代碼,為您展示如何在 Python 中編寫方法。
清單 1. 定義函數的偽代碼
def myFunction(optional input data): initialize any local data actual statements that do the work optionally return any results
如您所見,在 Python 中,函數的基本組成部分是包裝器代碼,指明將被重用的一系列 Python 語句。函數可接受輸入參數,輸入參數在緊接著函數名(在本例中為 myFunction)之后的圓括號內提供。函數還可返回值(更為正式的說法是:對象),包括像 tuple 這樣的 Python 容器。
在真正著手構建函數之前,讓我們先來看看關于偽代碼的一些簡單卻重要的要點:
- ??? 請注意函數名中所用的字符大小寫:大多數字符都是小寫的,但在使用多個單詞連接成一個函數名時,后接的各單詞首字母應大寫(例如,myFunction 中的 F)。這就是所謂的駝峰式大小寫風格 (camel casing),是 Python 和其他編程語言中廣泛采用的一種技術,可使函數的名稱更易閱讀。
- ??? 函數定義中的程序語句采用了縮進式排版,函數體由 Python 語句塊構成,它們也必須像循環或條件語句那樣縮進。
- ??? 函數定義的第一行也稱為方法簽名 (method signature),以 def 開頭(def 是 define 這個單詞的縮寫)。
- ??? 方法簽名以冒號結尾,表示下面的代碼行是函數體。
至此,您或許已認可了使用方法的好處。那么讓我們投入進去,開始編寫函數吧。“Discover Python, Part 6: Programming in Python, For the fun of it” 中使用了一個 for 循環來創建乘法表。清單 2 展示了同樣的概念,但本例中創建的是一個函數,用于封裝乘法表計算背后的邏輯。
清單 2. 第一個函數
>>> def timesTable(): ... for row in range(1, 6): ... for col in range(1, 6): ... print "%3d " % (row * col), ... print ... >>> timesTable() 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25 >>> t = timesTable >>> type(t)>>> t >>> t() 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
timesTable 函數定義起來非常簡單,它不接受任何輸入參數,也不返回任何結果。函數體幾乎與 “Discover Python, Part 6” 中的語句完全相同(但該文章中的乘法表為從 1 到 10)。為了調用 方法,并使其發揮作用,只需輸入函數名后接圓括號即可。本例中還輸出了乘法表。
在 Python 中,函數是一類對象,與整型變量和容器對象相同。因而,您可以將函數指派給一個變量(切記,在 Python 中變量是動態類型化的)。在清單 2 中,我們將 timesTable 函數指派給變量 t。接下來的兩行代碼表示變量 t 確實指向函數。最后,我們使用變量 t 調用 timesTable 函數。
函數:動態更改邏輯
清單 2 中的 timesTable 函數不復雜,但也不是特別有用。更有用的示例允許您指定用于生成乘法表的行數和列數 ―― 換言之,允許您在調用函數時動態地更改函數的操作方式。在函數定義中使用兩個輸入參數即可實現這一功能,如清單 3 所示。
清單 3. 更好的乘法表函數
>>> def timesTable2(nrows=5, ncols=5): ... for row in range(1, nrows + 1): ... for cols in range(1, ncols + 1): ... print "%3d " % (row * cols), ... print ... >>> timesTable2(4, 6) 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 >>> timesTable2() 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25 >>> timesTable2(ncols=3) 1 2 3 2 4 6 3 6 9 4 8 12 5 10 15
兩個乘法表函數的定義非常相近,但清單 3 中的函數有用得多(通過清單 3 中的 3 次調用即可看出這一點)。為函數添加此附加功能的方法非常簡單:提供名為 nrows 和 ncols 的兩個輸入參數,允許在調用函數時更改乘法表的大小。這兩個參數隨后會被提供給生成乘法表的兩個 for 循環。
關于 timesTable2 函數的另一要點就是兩個輸入參數有默認值。在函數簽名中為參數提供默認值,方法是在參數名后添加等號和值,例如 nrows=5。默認參數使程序獲得了更高的靈活性,因為在您調用函數時,可以包含兩個輸入參數,也可以僅包含一個輸入參數,甚至可以一個參數都不包含。但這種方法可能會導致某些問題。如果您在函數調用期間未指定全部參數,則必須顯式地寫出您所指定的參數的名稱,以使 Python 解釋器能夠正確地調用函數。最后一個函數調用正體現了這一點,它顯式地調用了帶有 ncols=3 的 timesTable2 函數,函數創建了一個 5 行(默認值)3 列(所提供的值)的乘法表。
函數:返回數據
使用方法時,人們最希望獲得的結果并非總是乘法表。您可能希望完成一次計算,并將計算結果值返回給調用代碼。有時要實現這兩個目的,需要分別調用不返回任何數據的調用方法(子例程)和返回值的方法(函數)。但在 Python 中,您無需擔心這些語義問題,因為通過使用 return 語句,幾乎可以相同的方式實現這兩個目的(參見清單 4)。
清單 4. 在函數中返回一個值
>>> def stats(data): ... sum = 0.0 ... for value in data: ... sum += value ... return (sum/len(data)) ... >>> stats([1, 2, 3, 4, 5]) # Find the mean value from a list 3.0 >>> stats((1, 2, 3, 4, 5)) # Find the mean value from a tuple 3.0 >>> stats() Traceback (most recent call last): File "", line 1, in ? TypeError: stats() takes exactly 1 argument (0 given) >>> stats("12345") Traceback (most recent call last): File " ", line 1, in ? File " ", line 4, in stats TypeError: unsupported operand type(s) for +=: 'float' and 'str'
這個簡單的函數遍歷 data(假設 data 為一個容納有數字數據的 Python 容器),計算一組數據的平均值,然后返回值。函數定義接受一個輸入參數。平均值通過 return 語句傳回。當您調用帶有包含數字 1 到 5 的 list 或 tuple 的函數時,返回值會顯示在屏幕上。如果調用不帶任何參數的函數、帶非容器數據類型的函數或帶內含非數字數據的容器的函數,就會導致出錯。(在此類情況下拋出錯誤是很有意義的。更高級的處理方法應包含恰當的錯誤檢查和處理,以應對這些情況,但這不在本文討論范圍內。)
此示例已經非常有用,但還可使它更強大,如清單 5 所示。在 Python 中,函數可返回任何有效的對象類型,包括容器類型在內。因此,您可以計算多個數量,并輕松地將多個結果返回給調用語句。
清單 5. 返回復合值
>>> def stats(data): ... sum = 0.0 ... for value in data: ... sum += value ... mean = sum/len(data) ... sum = 0.0 ... for value in data: ... sum += (value - mean)**2 ... variance = sum/(len(data) - 1) ... return (mean, variance) ... >>> stats([1, 2, 3, 4, 5]) (3.0, 2.5) >>> (m, v) = stats([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> print m, v 5.0 7.5
為了從一個函數中返回多個值,要將其括在一個括號中并以逗號分隔 ―― 換句話說,創建并返回一個 tuple。新 stats 函數的函數體要略加修改,以計算數字序列的樣本方差。最后,正如 stats 函數的兩次調用所示,tuple 值可作為一個 tuple 存取,也可將其解包為各自的分量。
模塊:簡化代碼重用
至此,您或許已相信了代碼重用的價值。但即便是使用函數,您依然需要在打算使用函數時重新輸入函數體。例如,當您打開一個新的 Python 解釋器時,必須鍵入之前所創建的所有函數。幸運的是,您可以使用模塊 將相關函數(和其他 Python 對象)封裝在一起,將其保存在一個文件中,然后將這些已定義好的函數導入到新 Python 代碼內,包含于 Python 解釋器之中。
為介紹在 Python 中使用模塊的方法,我們將重用清單 5 中的 stats 方法。有兩個選擇:您可以從與本文相關的壓縮文件中提取名為 test.py 的文件,也可以在編輯器中鍵入函數,然后將文件保存為 test.py。完成上一步后,在您保存 test.py 的目錄中啟動一個新的 Python 解釋器,然后輸入如清單 6 所示的語句。
清單 6. 使用模塊
>>> import test >>> test.stats([1, 2, 3, 4, 5, 6, 7, 8, 9]) (5.0, 7.5) >>> from test import stats >>> (m, v) = stats([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> print m, v 5.0 7.5
第一行 import test 打開文件 test.py 并處理文件中的各條語句。這里僅定義了 stats 函數,但若需要,您還可定義更多的函數。調用 stats 函數時,應以模塊名 test 作為函數前綴。之所以使用這種復雜的名稱,是出于作用域 方面的考慮,作用域表示一個程序內名稱的有效范圍。為告知 Python 您要調用的是哪個 stats 方法,就必須提供完整的名稱。這一點非常重要,因為您可能擁有多個名稱相同的對象。作用域規則可幫助 Python 判斷您想使用的對象。
第三行 from test import stats 也打開了文件 test.py,但它隱式地將 stats 方法置入當前文件的作用域內,以使您能夠直接調用 stats 函數(無需使用模塊名)。明智地使用 from ... import ... 語法可使您的程序更簡潔,但過度的使用也會導致混淆,甚至出現更糟糕的作用域沖突錯誤。不要濫用您的新武器!
模塊庫
使用 Python 編程語言的一個主要好處就是大型的內置式標準庫,可作為 Python 模塊訪問。常用模塊示例如下:
- ??? math 包含有用的數學函數。
- ??? sys 包含用于與 Python 解釋器交互的數據和方法。
- ??? array 包含數組數據類型和相關函數。
- ??? datetime 包含有用的日期和時間處理函數。
由于這些都是內置模塊,因此您可以通過幫助解釋器來了解更多相關內容,如清單 7 所示。
清單 7. 獲得關于 math 模塊的幫助信息
>>> help(math) Traceback (most recent call last): File "", line 1, in ? NameError: name 'math' is not defined >>> import math # Need to import math module in order to use it >>> help(math) Help on module math: NAME math FILE /System/Library/Frameworks/Python.framework/Versions/2.4/lib/ python2.4/lib-dynload/math.so DESCRIPTION This module is always available. It provides access to the mathematical functions defined by the C standard. FUNCTIONS acos(...) acos(x) Return the arc cosine (measured in radians) of x. asin(...) asin(x) Return the arc sine (measured in radians) of x. ...
math 模塊的幫助輸出展示了所支持的大量數學函數,包括 sqrt 函數在內。您可以利用此函數將您的樣本方差計算轉換為樣本標準差計算,如清單 8 所示。
清單 8. 使用多個模塊
>>> from math import sqrt >>> from test import stats >>> (m, v) = stats([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> print m, sqrt(v) 5.0 2.73861278753
如您所見,您可以將多個模塊導入到一個 Python 程序中。在大型、內置的模塊庫與更大量的公用庫(其中許多都是開放源碼的)的共同協助下,您很快也會成為一名懶惰 ―― 也就是杰出 ―― 的程序員。
可執行文件
導入一個模塊時,Python 解釋器會處理模塊文件內的各行。實際上,您可以調用 Python 解釋器使其僅處理包含于一個文件中的一個 Python 程序。在基于 UNIX? 的操作系統中,您可以輕松創建可執行的文件,如清單 9 所示。
清單 9. 一個完整的 Python 程序
#!/usr/bin/env python def stats(data): sum = 0.0 for value in data: sum += value mean = sum/len(data) sum = 0 for value in data: sum += (value - mean)**2 variance = sum/(len(data) - 1) return(mean, variance) (m, v) = stats([1, 2, 3, 4, 5, 6, 7, 8, 9]) print "The mean and variance of the values " \ "from 1 to 9 inclusive are ",m, v
觀察上例,您應該會產生幾分好感,將 Python 程序置于文件內,并使其運行是如此簡單。本例與 test.py 文件中的代碼之間惟一的差異就是包含了第一行。在基于 UNIX 的操作系統中,本行會使 Python 解釋器自動啟動,并在終止前處理文件中的語句。本示例中的其他行定義了 stats 函數、調用了函數,并輸出了結果。
要運行本文件中的語句,您需要啟動一個 Python 解釋器,并讓它去讀取和處理文件的內容。為實現這一目的,您必須首先將清單 9 中的示例輸入到一個名為 mystats.py 的文件中,也可從與本文相關的壓縮文件中提取文件。進入包含此文件的目錄,然后按清單 10 中所示命令執行。注意對于 Microsoft? Windows? 操作系統而言,僅應使用第一條命令;其他命令是供 UNIX 系統(如 Linux? 或 Mac OS X)使用的。
清單 10. 執行 Python 程序
rb% python mystats.py The mean and variance of the values from 1 to 9 inclusive are 5.0 7.5 rb% chmod +x mystats.py rb% ./mystats.py The mean and variance of the values from 1 to 9 inclusive are 5.0 7.5
清單 10 中的命令展示了運行一個包含于文件之中的 Python 程序的方法。第一條命令以文件名調用 Python 解釋器,無論使用哪種系統安裝 Python、Python 解釋器位于哪個目錄下,這種方法都有效。第二條命令 chmod 使包含 Python 程序的文件成為可執行文件。第三條命令告訴操作系統運行程序。這是通過使用 env 程序實現的,這是一種獨立于操作系統的技術,用于定位和運行程序 ―― 本例中是 Python 解釋器。
重用與縮減
本文介紹了如何在 Python 中編寫可重用代碼。討論了如何在 Python 程序中使用方法或可重用塊。方法可接受輸入參數,也可返回數據,包括容器數據類型在內。這種功能使方法成為一種可處理大量問題的強大途徑。本文還介紹了模塊,模塊可使您將相關方法及數據合并入一個有組織的層次結構中,而此結構可方便地在其他 Python 程序中重用。最后還介紹了如何將所有這些內容組合在一起以創建一個功能完整、獨立的 Python 程序。您已經看到,代碼的重用也就意味著您的工作量縮減。對于程序員而言,懶惰是一種優勢而非陋習。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
