第六章 函數和調試
在本章中,你將學習以下兩方面的基本知識:
-
- 函數
- 使用Python調試包pdb
函數是程序重要的組成部分,你將在第七章中使用它們,並學習如何使用隨機化來模擬DNA突變。Python調試包pdb可以將程序放緩,按步執行找到出錯的地方。
1.函數
函數是組織好的代碼塊,並提供一種方法(函數名稱)可以傳遞一些值進行計算,然後返回結果。程序可以通過調用函數名稱來使用函數中的代碼,將函數所需的值傳遞給函數中的代碼,然後返回結果,這種方式使用或“調用”通常稱為調用函數。Python有許多內建的函數,例如上述使用的print、input、open等等。
1.1 函數的優勢
函數可以將程序抽象、模塊化以及將代碼組織成輸入和輸出的可管理塊來創建大型程序的能力。
假設你需要計算某些內容,例如計算程序中幾個地方或不同程序中的分布均值。編寫函數後,你就可以在需要使用的地方調用函數,從而是你的程序:
-
- 簡短,因為減少了重復代碼
- 更容易測試,因為可以單獨測試函數
- 易於理解,函數使程序組織更清晰
- 更可靠,因為在重用函數時,代碼少,因此出錯機會更少
- 開發快,因為你可能已經編寫了一些處理基本統計數據的函數,使用時只需調用計算均值的函數而無需再次編寫。或者,你找到了一個別人寫好的統計庫,不需要你編寫了。
函數本身也可以調用其它函數,通過編寫一組函數,每個函數都可以完成一項或幾項工作,然後通過各種方法將它們組合起來形成新的函數,最終結果是形成強大靈活的編程系統。在編程中,將問題分解為可以方便組合的函數集,使可以創建適應不斷變化的條件而只需花費最小代價。
所有這些內容的訣竅在於如何將代碼分區為函數。函數封裝了一些常用的東西,而不是只調用一次;函數應該只做一件事,代碼只有一兩頁。這些不是真正的標準,但可以幫助你將代碼劃分為適用於函數的可管理塊。
1.2 編寫函數
讓我們看看如何使用函數,然後看看它們是如何定義的。
調用函數,傳遞數據給函數數,然後接收返回值。例如,假設你想實現一個函數addACGT,給定一些DNA,將“ATCG”添加到DNA的末端並返回新的DNA。在Python中,通常鍵入函數名稱來調用函數,然後是帶括號的參數列表。例如,使用參數dna嗲用函數addACGT:
addACGT(dna)
例子6-1演示了該函數,詳細說名了原理。
例子6-1 DNA末端加ACGT函數
#!/usr/bin/env python # A program with a subroutine to append ACGT to DNA ################################################################################ # Subroutines for Example 6-1 ################################################################################ # Here is the definition for subroutine "addACGT" def addACGT(dna): dna += ‘ACGT‘ return dna ################################################################################ # The original DNA dna = ‘CGACGTCTTCTCAGGCGA‘ # The call to the subroutine "addACGT". # The argument being passed in is dna; the result is saved in longer_dna longer_dna = addACGT(dna) print("I added ACGT to %s and got %s\n\n" % (dna, longer_dna)) exit()
例子6-1輸出如下:
I added ACGT to CGACGTCTTCTCAGGCGA and got CGACGTCTTCTCAGGCGAACGT
我們現在來看下這段代碼,首先註意的是程序有兩個部分。第一部分是函數addACGT定義,第二部分是程序主體執行。在Python中,程序是順序執行,若函數addACGT定義在函數調用步驟後面,程序會出錯。如你所見,例子6-1非常簡單。它首先將一些DNA存儲在變量dna中,然後該變量作為參數傳遞給函數調用,接著函數返回的值保存在變量longer_dna中,最後打印,程序退出。
接下來,我們將了解函數定義以及它如何使用作用域的原理。
2.函數和作用域
函數由保留字def定義,後接函數名稱和圓括號();任何傳入參數和自變量必須放在圓括號中,圓括號支架可以用於定義參數;函數內容以冒號起始,並且縮進;return語句結束函數,返回值或None。
在例子6-1中,函數名稱是addACGT,塊是縮進內容,這是函數定義:
def addACGT(dna): dna += ‘ACGT‘ return dna
現在我們看看函數中的內容。
函數就像是主程序的獨立輔助程序,它需要由自己的變量。你將在本專輯的函數中使用兩種類型的變量:
-
- 傳入函數的參數
- 函數內定義的參數,並且限制在函數範圍內使用
參數是指函數使用或調用時給予的值,參數的值通過函數名稱後面的括號進行傳遞。最後,大多數函數通過return語句返回函數結果,在例子6-1中,函數addACGT,返回了字符串。
2.1 參數
當你使用某些參數調用函數時,你在調用中使用的參數名稱在函數中並不重要,只有在函數中實際傳遞的參數值才是最重要的。
2.2 作用域
在Python中,使用一個變量時並不嚴格要求需要預先聲明它,但是在真正使用它之前,它必須被綁定到某個內存對象(被定義、賦值);這種變量名的綁定將在當前作用域中引入新的變量,同時屏蔽外層作用域中的同名變量。
3.命令行參數和列表
例子6-2是另一個使用函數的程序,你可以使用命令行提供程序所需的信息(例如,文件路徑或者DNA字符串),而不用交互式的輸出。例子6-2還顯示了如何使用列表,通過下標來訪問列表中特定元素。
對於命令行程序,鍵入程序名稱,然後鍵入程序的參數(如果有),然後按回車鍵啟動程序。在例子6-2中,當用戶鍵入程序名名稱時,接著鍵入程序計算所需的DNA字符串,程序調用戶返回如下結果:
AAGGGGTTTCCC The DNA AAGGGGTTTCCC has 4 G‘s in it!
例子6-2 在命令行中計算DNA中G的個數
#!/usr/bin/env python # Counting the number of G‘s in some DNA on the command line import sys ################################################################################ # Subroutines for Example 6-3 ################################################################################ def countG(dna) # return a count of the number of G‘s in the argument dna # Use the fourth method of counting nucleotides in DNA, as shown in # Chapter Four, "Motifs and Loops" count = dna.count(‘G‘) + dna.count(‘g‘) return count } # Collect the DNA from the arguments on the command line # when the user calls the program. # If no arguments are given, print a USAGE statement and exit. # $0 is a special variable that has the name of the program. USAGE = "%s DNA\n\n" % sys.argv[0] # sys.argv is an array containing all command-line arguments. # # If len(sys.argv) < 2 , the test will fail and the print USAGE and exit # statements will be called. if len(sys.argv) < 2: print(USAGE) exit() # Read in the DNA from the argument on the command line. dna = sys.argv[1] # Call the subroutine that does the real work, and collect the result. num_of_Gs = countG(dna) # Report the result and exit. print("\nThe DNA $dna has %s G\‘s in it!\n\n" % num_of_Gs) exit()
現在讓我們看看這個程序是如何工作的,同時查看些新的用法。Python中內置模塊sys提供了程序與python解釋器交互的函數和變量,因此你可以方便的使用命令行參數。sys.argv列表中0索引位置是從命令行調用的程序名稱。在例子6-2中,我們使用了if語句來判斷參數是否為空(len(sys.argv)<2),如果為空,則打印USAGE退出。
另外一點是,例子6-2使用索引來提取元素。例如,提取第一個參數,sys.argv列表中索引為0的元素:
sys.argv[0]
4.模塊和庫中的函數
當你開始編寫程序時,你會發現從你現有程序中復制它們並將它們粘貼到新程序中,然後這樣的代碼在多個程序中出現,使得代碼有點冗余,有時需要修改函數時,你必須得修改所有的副本。有一種方便方法是將函數收集到稱為模塊或庫的文件中,然後使用Python中內置函數import,導入庫或模塊中的函數。
我們將定義一個BeginPythonBioinfo.py,然後你可以將所有函數定義在其中。現在,如果要使用BeginPythonBioinfo.py中任何函數,你只需要將以下語句放在代碼開頭:
import BeginPythonBioinfo
註意,不需要後綴.py,這是Python處理模塊名稱的方式。關於使用模塊將在函數還有最後一件事要知道:Python程序需要知道在哪裏找到模塊,如果是在一個文件夾下完成所有工作,那麽一切正常。(如果不是,超出本專輯範圍了,請閱讀Python相關文檔)。
5.修復代碼中錯誤
現在讓我們來看看當程序出錯時該怎麽辦。
程序可能會以多種方式出錯,也許根本不會運行。查看錯誤消息,尤其是第一行或第二行錯誤消息,會找出問題,例如,縮進錯誤,少了括號等等。
另外,程序能運行,但結果不符合,那麽程序的邏輯有些問題。python有幾種方法可以幫助你查找和修復程序中的錯誤。
5.1 使用註釋和print語句修復錯誤
有時你可以通過選擇性註釋掉程序的各個部分來找到導致問題出現的部分;你還可以添加print語句來檢查某些變量正在執行的操作,這些都是歷史悠久的編程技巧,幾乎適用於任何編程語言。
5.2 使用python調試工具
代碼中的錯誤問題是一旦程序開始運行,你只能看到輸出,不能看到程序正在執行的步驟。使用Python調試工具處理程序中錯誤,可以逐步詳細檢查程序,能夠快速解決問題。
某些情況下,Python調試器無法很好地處理:例如:依賴時序考慮的交互過程。調試器一次只能檢查一個程序,並且在檢查時,它會停止程序,因此會產生與其他進程的時序交互問題。
本節將介紹Python內置調試模塊pdb,及其重要的功能。
#!/usr/bin/env python
import pdb def printbase(dna): pdb.set_trace() for b in dna: print(b) dna = ‘ATAGtcagatgataagtaga‘ printbase(dna)
上述代碼中,pdb.set_trace()為程序設置斷點,斷點告訴調試器停止執行,以便你可以在代碼中查找錯誤。Python調試器允許你以格之格方式設置斷點,程序運行僅在到達帶有斷點的語句時停止檢查它,這樣你就不必逐步執行每行代碼。另外,Python有許多開發工具自帶調試工具(例如,Pycharm、Eclipse等等)。
6.練習
6.1 編寫一個函數來連接兩個DNA字符串
6.2 編寫一個函數返回DNA字符串中每個核苷酸的百分比。
6.3 編寫函數提示用戶輸入任何內容,並返回用戶收集的答案。
6.4 編寫函數查找命令行參數,例如,-help、-h和--help。
6.5 編寫函數檢查文件是否存在,是否為常規文件,且大小為非零。
6.6 編寫一個包含函數的模塊,該函數返回有關DNA序列的各種統計數據,例如長度、GC含量,polyT序列的存在與否,或其它信息。
6.7 閱讀有關調試器的文檔,並通過編程來熟悉它的使用。
第六章 函數和調試