在大概3個月之前,Python對我來說一直是個迷。然而,就在3個月前我經理給我一個任務——刪除(替換)所有專案原始碼檔案中包含特定幾行內容的所有註釋。整個專案原始碼的大小有1G,在Linux伺服器(中高檔)上編譯需要半個多小時,可見程式碼量之大,不可能手動去一個一個改。肯定得用指令碼去處理,於是我想到了Python。在這之前沒有接觸過Python,花了2個星期一頓惡補之後,總算順利交差了。
一直很想和大家分享一下碰到的問題及我如何解決的(可能我的方案並不好,但是他能夠解決我的問題),但一直拖到現在是因為我感覺我還對Python的瞭解還不夠。因為要在短時間內完成上面交下來的任務,在學習Python的時候,都是走馬觀花,對解決自己的問題不相關的直接跳過,看資料也靜不下心,腦海裡都是問題。前幾天我靜下心把Python的書從頭到尾瀏覽了一遍,感覺現在是時候要進行總結了。
本文的主要內容如下:
- 問題描述
- 解題思路
- 程式碼實現
- Python的特點
1、問題描述
專案原始碼很大,屬於C/C++混合的那種,程式設計風格也很多樣,有’.c’、’.cc’、’cpp’、’.h’、’.hh’等檔案。我要完成的任務是:把包含特定幾行內容的註釋刪掉,如(宣告:下面的內容只是我隨便舉的一個例子,專案原始碼中不涉及下面的內容。)
/*
* Copyright 2002 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* – Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* – Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*/
但是格式有很多種,如有的在“ Copyright 2002 Sun Microsystems, Inc. All rights reserved.”前面有一段關於本原始碼檔案的描述、有的在“from this software without specific prior written permission.”後面有一段關於本原始碼檔案的描述、有的是C++風格的註釋用”//”,而不是“/**/”、還有的沒有
“ * – Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.”等等還有其他一些。總之一句話,我要刪除的包含特定幾行內容的註釋有很多中格式!
於是我決定要用Python來編寫指令碼處理。要匹配特定的內容,我想到了用正規表示式,但苦於不知道如何去構建正則來匹配上面描述的內容(您知道的話,希望能夠告訴我)!我只有另闢路徑了。
2、解題思路
我的思路——要刪除所有專案原始碼中包含特定幾行內容的註釋,指令碼要滿足以下幾點功能:
- 指令碼要能夠遍歷所有的原始碼檔案(’.c’、’.cc’、’cpp’、’.h’、’.hh’),並只處理上面的幾種型別的檔案
- 找出包含特定幾行內容的註釋,並刪除之
- 能夠處理一些特殊情況,如軟連線檔案
上面的幾點的處理步驟可以表示如下:
Step 1:輸入要處理原始碼資料夾名,或者原始碼檔名;
Step 2:如果是檔名,檢查檔案的型別是否為’.c’、’.cc’、’cpp’、’.h’、’.hh’,否則不處理;
Step 3:檢查檔案是否是軟連線,如果是軟連線則不處理;
Step 4:查詢檔案中是否存在匹配的註釋,存在則刪掉,否則不處理;
Step 5:如果是資料夾,則對資料夾中的每個檔案、資料夾進行處理,轉Step2.
思路很明確,關鍵是如何查詢檔案中是否包含匹配的內容,並刪除!還有就是,對於一個沒用過Python等指令碼語言的人來說,如何編碼實現也是一個問題!
如何確定註釋是否為包含特定幾行內容的註釋?我的思路如下:(因為正規表示式學的不好,只有通過下面的方法了)
- 如果是/*、//則記錄下當前的檔案行數,即行號startLine
- 以行為單位查詢是否存在特定的幾行,如“ Copyright 2002 Sun Microsystems, Inc. All rights reserved.”等等
- 直到遇到*/,或註釋結束了(對於//)。如果存在,則記錄下註釋結束的行號endLine
- 最後,刪掉這從startLine ~ endLine的內容。
3、程式碼實現
廢話我不多說了,直接按照上面的例項實現程式碼,如果你對Python不熟,請參閱相關資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
#!/usr/bin/env python #Filename: comment.py import os, sys, fileinput #------------------------------------------------------------- def usage(): print u''' help: comment.py <filename | dirname> [dirname]: Option, select a directory to operate [filename]: Option, select a file to operate Example: python comment.py /home/saylor/test ''' #-------------------------------------------------------------- def commentFile(src, fileList): ''' description: comment files param src: Operate file name ''' #if file exist? if not os.path.exists(src): print 'Error: file - %s doesn\'t exist.'% src return False if os.path.islink(src): print 'Error: file - %s is just a link, will not handle it.' return False filetype = (os.path.splitext(src))[1] if not filetype in ['.c','.h']: return False try: if not os.access(src, os.W_OK): os.chmod(src, 0664) except: print 'Error: you can not chang %s\'s mode.'% src try: inputf = open(src, 'r') outputfilename = src + '.tmp' outputf = open(outputfilename, 'w') beginLine = 0 endLine = 100000000 isMatched = False #-----find the beginLine and endLine ------------------- for eachline in fileinput.input(src): if eachline.find('/*') >= 0: beginLine = fileinput.lineno() if eachline.find('Copyright 2002 Sun Microsystems, Inc. All rights reserved.') >= 0: isMatched = True if eachline.find('*/') >= 0 and isMatched: endLine = fileinput.lineno() break #-----delete the content between beginLine and endLine----- print beginLine, endLine lineNo = 1 for eachline in inputf: if lineNo < beginLine: print eachline outputf.write(eachline) elif lineNo > endLine: print eachline outputf.write(eachline) lineNo = lineNo + 1 inputf.close() outputf.close() os.rename(outputfilename, src) fileList.append(src) except: print 'Error: unexcept error.' inputf.close() outputf.close() return True #-------------------------------------------------------------- def commentDir(src, fileList): ''' description: comment files in src(dir) param src: operate files in src(dir) ''' #if dir exist? if not os.path.exists(src): print 'Error: dir - %s is not exist.'%s (src) return False filelists = os.listdir(src) for eachfile in filelists: eachfile = src + '/' +eachfile if os.path.isdir(eachfile): commentDir(eachfile, fileList) elif os.path.isfile(eachfile): commentFile(eachfile, fileList) return True #-------------------------------------------------------------- def main(): if len(sys.argv) < 2: usage() sys.exit(1) src = sys.argv[1] if os.path.isdir(src): dire = os.path.abspath(src) dirFlag = True elif os.path.isfile(src): fl = os.path.abspath(src) dirFlag = False else: print 'Error' fileList = [] if dirFlag: commentDir(dire, fileList) else: commentFile(fl, fileList) if fileList: print 'Successful handle file: ...' for eachfile in fileList: print eachfile print 'Done' return True #-------------------------------------------------------------- if __name__ == '__main__': main() |
4、Python的特點
Python入門我強烈推薦下面的資料,深入學習請閱讀其它資料:
《A Byte of Python》http://www.swaroopch.com/notes/Python
《簡明 Python 教程》http://woodpecker.org.cn/abyteofpython_cn/chinese/
Python的設計哲學是“優雅”、“明確”、“簡單”。因此,Perl語言中“總有多種方法來做同一件事”的理念在Python開發者中通常是難以忍受的。Python開發者的哲學是“用一種方法,最好是隻有一種方法來做一件事”。在設計Python語言時,如果面臨多種選擇,Python開發者總會拒絕花哨的語法,而選擇明確的沒有或者很少有歧義的語法。由於這種設計觀念的差異,Python原始碼通常認為比Perl具備更好的可讀性。
Python開發人員儘量避開不成熟或者不重要的優化。一些針對非重要部位的加快執行速度的補丁通常不會被合併到Python內。所以很多認為Python很慢。不過,根據二八定律,大多數程式對速度要求不高。在某些對執行速度要求很高的情況,Python程式設計師傾向於使用JIT技術,或者用使用C/C++語言改寫這部分程式。目前可用的JIT技術是Pysco。Cython可以將Python程式碼轉換成C程式碼。
相對於Lisp這種傳統的函數語言程式設計語言,Python對函數語言程式設計只提供了有限的支援。有兩個標準庫(functools, itertools)提供了Haskell和Standard ML中久經考驗的函數語言程式設計工具。
雖然Python可能被粗略地分類為「指令碼語言」(script language),但實際上一些大規模軟體開發計劃例如Zope、Mnet及BitTorrent,Google也廣泛地使用它。Python的支持者較喜歡稱它為一種高階動態程式語言,原因是「指令碼語言」泛指僅作簡單程式設計任務的語言,如shell script、JavaScript等只能處理簡單任務的程式語言,並不能與Python相提並論。
Python本身被設計為可擴充套件的。並非所有的特性和功能都整合到語言核心。可以使用C語言、C++、Cython來編寫擴充套件模組。Python直譯器本身也可以被整合到其它需要指令碼語言的程式內。因此,很多人還把Python作為一種「膠水語言」(glue language)使用。使用Python將其他語言編寫的程式進行整合和封裝。在Google內部的很多專案使用C++編寫效能要求極高的部分,然後用Python呼叫相應的模組。
Python的特點:
- 1、第一行是特殊形式的註釋:它被稱作 組織行 ——原始檔的頭兩個字元是#!,後面跟著一個程式。這行告訴你的Linux/Unix系統當你 執行 你的程式的時候,它應該執行哪個直譯器。建議使用這種形式——#!/usr/bin/env python,而不是——#!/usr/bin/python。
- 2、縮排很重要。Python使用縮排而不是一對花括號來劃分語句塊。
- 3、關鍵引數的概念很有用
- 4、None 返回”沒有任何東西”,每一個函式預設返回None
- 5、pass 空語句塊
- 6、文件字串,__doc__,沒多大用。但是一個好的Python程式,應該要有文件字串,且一般遵循:“文件字串的慣例是一個多行字串,它的首行以大寫字母開始,句號結尾。第二行是空行,從第三行開始是詳細的描述。 ”
- 6、python中引入模組後(import)首先就要執行模組的主塊,當然模組中可能全是函式。如果要避免使用模組名稱:from 模組名 import 符號名,那麼使用該符號名就不用使用模組名+點號+符號名,但是不推薦,容易造成程式不容易讀,而且容易出錯(特別是在python簡潔而簡單的語法的基礎上) import… as … 起一個別名
- 7、模組的__name__屬性,相當有用,解決了import的缺點,可以實現如果不是執行的本模組而被呼叫,不呼叫主塊
123456#!/usr/bin/env python# Filename: using_name.pyif __name__ == '__main__':print 'This program is being run by itself'else:print 'I am being imported from another module'
- 8、刪除一個變數/名稱,你將無法再使用該變數——它就好像從來沒有存在過一樣。
- 9、可以使用內建的dir函式來列出模組定義的識別符號。識別符號有函式、類和變數。當你為dir()提供一個模組名的時候,它返回模組定義的名稱列表。如果不提供引數,它返回當前模組中定義的名稱列表
- 10、元組語法與list相似,意義相當於列舉,可以為空,如果只含有一個元素,需要加逗號以區別於表示式(“one”, )
- 11、元組最通常的用法是用在列印語句中,可以使用格式控制符
123456#!/usr/bin/env python# Filename: print_tuple.pyage = 22name = 'Swaroop'print '%s is %d years old' % (name, age)print 'Why is %s playing with that python?' % name
- 12、有一個內建的字典型別,但是沒有衝突的解決方案,但這確實是字典的定義,想要更好的結構就自己實現吧。語法:{key:value, key1:value1,…}
- 13、序列的概念:列表、元組和字串都是序列,支援索引操作符和切片操作符。索引操作符讓我們可以從序列中抓取一個特定專案。切片操作符讓我們能夠獲取序列的一個切片,即一部分序列。索引可以是負數,在那樣的情況下,位置是從序列尾開始計算的。序列的神奇之處在於你可以用相同的方法訪問元組、列表和字串。
- 14、如果你想要複製一個列表或者類似的序列或者其他複雜的物件(不是如整數那樣的簡單 物件 ),那麼你必須使用切片操作符來取得拷貝。如果你只是想要使用另一個變數名,兩個名稱都 參考 同一個物件,那麼如果你不小心的話,可能會引來各種麻煩。[淺拷貝和深拷貝的關係]
- 15、str類有很多方法,如果要非常熟悉str的操作,參考help(str)
- 16、剩下的就是掌握很多系統庫了,這個要靠經驗,比如說os.system(命令)可用於執行shell命令,瞭解的庫越多,python就會讓你完成更強大的功能。
- 17、接下來是物件導向,基本概念一樣,this由代替self, 而且這個名字不一定要寫成self,任何名字都可以,這也帶來了一個缺點,你必須在形參裡面指定,呼叫函式時不用傳遞該引數。
建構函式:__init__(self, ……)
解構函式:__del__ 物件滅亡時或者呼叫del時被呼叫
Python中所有的類成員(包括資料成員)都是公共的 ,所有的方法都是有效的 。只有一個例外:如果你使用的資料成員名稱以 雙下劃線字首 比如__privatevar,Python的名稱管理體系會有效地把它作為私有變數。
支援多重繼承 - 18、如果你已經厭煩了java、c++的讀寫檔案,那麼python會讓你重新喜歡上檔案讀寫,python主張解決問題的方案越少越好,寫檔案就一個f = file(name, ‘w’),f.write(…)讀檔案也一樣,f = file(name),f.read或readline,最後close
- 19、cPickle和pickle是叫做儲存器的重要模組,可以非常方便地將一個物件儲存到一個檔案,然後再取儲存從檔案中取出來pickle.dump(object, file object),構造物件時,pickle.load(file object) [儲存、取儲存]
- 20、異常:raise,except,try…finally
- 21、sys模組和os模組有很多強大功能。
- 22、在函式中接收元組和列表當要使函式接收元組或字典形式的引數的時候,有一種特殊的方法,它分別使用*和**字首。這種方法在函式需要獲取可變數量的引數的時候特別有用。
- 23、lambda形式:lambda語句被用來建立新的函式物件,並且在執行時返回它們。lambda語句用>來建立函式物件。本質上,lambda需要一個引數,後面僅跟單個表示式作為函式體,而表示式的值被這個新建的函式返回。注意,即便是print語句也不能用在lambda形式中,只能使用表示式。
- 24、exec、eval、assert、repr函式和反引號用來獲取物件的可列印的表示形式。你可以通過定義類的__repr__方法來控制你的物件在被repr函式呼叫的時候返回的內容。
最後,感謝實習所在公司給我足夠的時間和機會去學習新的東西。還有要感謝學習Python期間,編寫參閱資料的人。期間我參閱了下面的資料(包括上面推薦的資料):
- wiki:http://zh.wikipedia.org/zh/Python
- Chinaunix的Python論壇資料
- 《OReilly-Learning-Python-4th-Edition-Oct-2009》
- 當然還有前面提到過的入門資料:簡明 Python 教程(中),對於的英文版是A Byte of Python