1. 程式人生 > >WC項目——python實現

WC項目——python實現

排查 難度 設置 exc ins 自己 != 空字符 eight

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實現