WC項目——python實現
GitHub地址:https://github.com/Amy-CC/SCHOOL.git
項目要求:
實現一個統計程序,它能正確統計程序文件中的字符數、單詞數、行數,以及還具備其他擴展功能,並能夠快速地處理多個文件。
- 具體功能(已實現)
- -c 返回文件字符數
- -w 返回詞的數目
- -l 返回行數
擴展功能(已實現)
- -s 遞歸處理目錄下符合條件的文件
- -a 返回更復雜的數據(代碼行 / 空行 / 註釋行)
- -x 命令行輸入-x參數,程序顯示圖形界面,用戶可以通過界面選取單個文件,程序就會顯示文件的字符數、行數等全部統計信息
PSP表格:
解題思路:
看到題目時,我先想到的是實現基礎功能。首先考慮的是考慮用sys模塊輸入,使其能在命令行中輸入。然後考慮文件的打開問題,我使用的是py文件所在的文件夾。-c功能考慮了所有的字符,-w功能先使用正則表達式去掉不是字母和空格的字符,然後通過空格分割來計數。-l則很簡單,使用len()函數就可以解決。
-w開始的時候想著用os.path模塊、os.walk函數,但是發現在這種情況下,我仍然需要對文件列表進行進一步的循環操作,所以放棄了os.walk函數,而是采用遞歸的方法。
-a我只考慮了c文件,因為c文件無法跟python的源文件同時判斷,由於註釋#的問題,在c語言中有含義,無法同時讀取。首先設置for循環,第一次判斷空行的情況,因為標準是少於或等於一個字符,我先把前後的空格去掉,就能判斷只有一個字符。第二次判斷註釋,用另一個函數,用正則判斷。第三次判斷直接通過len()判斷代碼行,避免前面兩行的影響。
在設計的過程中,我也查找了百度關於python的os庫,如何實現命令的輸入,文件的讀取,還有對於文件目錄的函數。我查看了關於python的書籍還有正則表達式的書籍。對於單元測試還是沒有完整覆蓋,所以最後采用的手動測試的方式。
設計實現過程:
因為首先做好的設計是關於基礎功能的,所以在後面實現擴展功能時,發現有著架構的問題,所以在後來需要把讀取文件的架構做一些修改:添加一個文件路徑列表,在讀取文件的時候直接使用readlines()函數,避免在計數的過程中出現文件變量被清空的情況。
在剛開始實現的時候,發現對於命令和文件名的判斷函數出現了問題,因為對於正則表達式的不熟悉,所以經過幾次的修改才能通過。
在考慮基礎功能時,開始寫的函數是每個命令每次都重讀一次文件,但是這太過於麻煩,需要多個for循環會提高復雜度,所以采用了一次讀取,多次使用的readlines()函數。而且readlines()函數會帶來問題,就是每個字符串都會出現一個換行符,所以我後來使用了rstrip()來去掉。
擴展功能-s剛開始出現了不知道怎麽判斷輸入的文件後綴名,後來通過分割再拼接的方式,實現讀取後綴名。而且因為後綴名有大小寫的問題,所以後面加了upper()函數。
擴展功能-a實現的時候因為re模塊函數不熟悉,在註釋的判斷上用了match()函數,發現由於match只針對開頭判斷,所以後面改變為search函數。
程序結構:
第一個類檢查輸入的正確性,第二個命令是各種操作的代碼。由判斷menu()函數來實現對其他操作函數的調用。
代碼說明:
一、第一個類:檢查正確性
1、判斷文件名的正確性
#檢查文件的合法性 def check_file(self): if re.match(r"[a-zA-Z0-9]*\..",self.file_name): self.file_list.append(os.path.abspath(self.file_name)) return True elif re.match("([\*\?$]+\.[a-zA-Z]+$)",self.file_name): return True print("The file name is not right.") return False
2、判斷命令名字的正確性
#檢查命令的合法性 def check_command(self): for i in range(1,self.order_number-1): if (self.order_list[i]!=‘-w‘) and self.order_list[i]!=‘-c‘ and self.order_list[i]!=‘-l‘ and self.order_list[i]!=‘-s‘ and self.order_list[i]!=‘-a‘: print("The order does not exist") return False break return True
二、第二個類:各個操作
1、統計字符數
輸入是readlines後的列表,只需統計除了最後換行符的字符串長度
#統計字符數 def re_chara(self,ff): count1 = 0 for i in ff: count1 += len(i.rstrip()) return count1
2、統計單詞數
先去掉最後換行符,再通過去掉非空格、非字母的字符,利用空格分割,統計非空字符串,即為單詞數。
#統計單詞數(由於readlines讀出的每一行都會有最後的換行符,所以用rstrip函數去掉) def re_word(self,ff): count2 = 0 string_after = "" for i in ff: string_after+=i.rstrip() new_a = re.sub("[^a-zA-z0-9\s]"," ",string_after) words = new_a.split() for word in words: if word != "": count2 += 1 return count2
3、統計行數
直接返回readlines()後的個數
#統計行數 def re_line(self,ff): return len(ff)
4、遞歸文件
通過分割拼接,得到完整路徑,如果符合後綴名,則放入列表;如果為文件夾則遞歸尋找。
#查看目錄中的文件 def file_recurse(self,pathh): #列出文件夾中所有的文件,包括文件夾 files = os.listdir(pathh) #分割輸入的文件名 p1=self.order_list[self.order_number-1].split(".") #拼接為後綴名 p = "."+ p1[1].upper() for f in files: #拼接為完整路徑 real_path = os.path.join(pathh,f) #如果是文件 if os.path.isfile(real_path): #分隔路徑 spl = os.path.splitext(real_path) if spl[1].upper()==p: #把路徑加入文件列表 self.file_list.append(real_path) #若是文件夾,則遞歸 if os.path.isdir(real_path): self.file_recurse(real_path)
5、統計空行、代碼行、註釋行
先判斷空行,去掉前後的空格等字符,則為顯示字符。
註釋行需另外函數來統計,因為需要標記/*的特殊情況。
代碼行則多於1個代碼並且不是註釋行。
#判斷註釋行 def explain_search(self,k,flag): if re.search(r"//",k) != None: flag = 1 return flag if re.search(r"/\*",k)!= None: flag = 0 if re.search(r"\*/",k)!= None: flag=1 return flag
#處理復雜數據 def complex_data(self,ff): #標誌空行 empty_l = 0 #標誌代碼行 code_num = 0 #標誌註釋行 explanation = 0 #用來/**/註釋方式的出現 flag = 0 for i in ff: k = i.strip() if len(k)<=1: empty_l += 1 elif all_operation.explain_search(self,i,flag)==True: explanation += 1 elif len(k)>1: code_num += 1 print("空行:"+str(empty_l)) print("代碼行:"+str(code_num)) print("註釋行:"+str(explanation))
6、菜單(調用操作、打開文件)
先遍歷文件列表,打開文件,再遍歷命令列表。
def menu(self): for f in self.file_list: try: with open(f) as file_object: ff = file_object f1 = ff.readlines() print(os.path.abspath(f)) for i in range(1, num-1): if self.order_list[i] == ‘-c‘: print("Characters: "+ str(all_operation.re_chara(self,f1))) elif self.order_list[i] == ‘-w‘: print("Words: "+ str(all_operation.re_word(self,f1))) elif self.order_list[i] == "-l": print("Lines: "+ str(all_operation.re_line(self,f1))) elif self.order_list[i] == ‘-s‘: continue elif self.order_list[i] == ‘-a‘: all_operation.complex_data(self,f1) else: print("the order is wrong, please try again") except FileNotFoundError: msg = "file: " + os.path.basename(f) + " does not exist" print (msg)
三、相當於main函數的主體
先判斷文件列表是否為空,選擇是否進入遞歸文件函數。
還要查詢遞歸後,是否有此類文件,有才執行菜單操作,否則退出。
num = len(sys.argv) #設置一個文件夾列表,存放文件路徑 file_list=[] #檢查是否有足夠的參數 if num<3: print("The parameters are not enough.") else: instance = check_every(sys.argv[num-1],file_list, num, sys.argv) if instance.check_file()==True and instance.check_command()==True: instan = all_operation(file_list, num, sys.argv) if len(file_list)==0: pp = os.path.abspath(__file__) instan.file_recurse(os.path.dirname(pp)) if len(file_list)==0: print("There is no such file") else: instan.menu()
測試結果:
各種命令的測試(正常源文件):
目標文件:
若是命令不符合:
若是文件名不符合:
-w操作的實現
目標目錄:
空白文檔:
單個字符文檔(字母):
單個字符文檔(非字母,而是控制字符):
項目總結:
1、因為是第一次使用python開發一個項目,所以很多東西都需要查詢資料,例如各種的模塊,由於各種模塊運用的還不是很熟練,而導致了一些混淆的現象,例如re模塊的match和search函數。
2、而且在過程中遇到了很多的困難,只有靠不斷的測試,查百度才了解了自己的錯誤原因,進行一遍遍的修正。了解到了查詢資料、測試的重要性。
3、盡管實現了基礎功能和擴展功能,項目的時間復雜度還是比較高的,還需要進一步的優化。
4、學會了使用GitHub來控制自己的版本操作,盡管上傳失敗了很多次,在一次次的摸索中終於上傳成功了。
5、明白了開始的規劃其實很重要,由於我之前規劃的不好,導致後來增加功能需要重新架構,浪費了很多的時間。
6、之前沒有學會寫一個函數及時測試, 導致後面的函數增加後,測試難度加大,需要用更多的時間來排查錯誤,下次需要改進。
WC項目——python實現