天天看點

《Python Cookbook(第2版)中文版》——1.13 通路子字元串

本節書摘來自異步社群《python cookbook(第2版)中文版》一書中的第1章,第1.13節,作者[美]alex martelli , anna martelli ravenscrof , david ascher ,高鐵軍 譯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

任務

擷取字元串的某個部分。比如,你讀取了一條定長的記錄,但隻想擷取這條記錄中的某些字段的資料。

解決方案

切片是個好方法,但是它一次隻能取得一個字段:

如果還需考慮字段的長度,struct.unpack可能更适合。比如:

如果想跳過“其餘部分”,隻需要給出正确的長度,拆解出theline的開頭部分的資料即可:

如果需要擷取5位元組一組的資料,可以利用帶清單推導(lc)的切片方法,代碼很簡單:

将字元切成一個個單獨的字元更加容易:

如果想把資料切成指定長度的列,用帶lc的切片方法通常是最簡單的:

在lc中調用zip,傳回的是一個清單,其中每項都是形如(cuts[k], cuts[k+1])這樣的數對,除了第一項和最後一項,這兩項分别是(0, cuts[0])和(cuts[len(cuts)-1], none)。換句話說,每一個數對都給出了用于切割的正确的(i, j),僅有第一項和最後一項例外,前者給出的是切割之前的切片方式,後者給出的是切割完成之後到字元串末尾的剩餘部分。lc利用這些數對就可以正确地将theline切分開來。

讨論

本節受到了perl cookbook 1.1的啟發。python的切片方法,取代了perl的substr。perl的内建的unpack和python的struct.unpack也非常相似。不過perl的手段更豐富一點,它可以用*來指定最後一個字段長度,并指代剩餘部分。在python中,無論是為了擷取或者跳過某些資料,我們都得計算和插入正确的長度。不過這不是什麼大問題,因為此類抽取字段資料的任務往往可以被封裝成小函數。如果該函數需要反複被調用的話,memoizing,通常也被稱為自動緩存機制,能夠極大地提高性能,因為它避免了為struct.unpack反複做一些格式準備工作。參見第18.5節中關于memoizing的更多細節。

在純python的環境中,struct.unpack作為字元串切片的一種替代方案,非常好用(當然不能和perl的substr比,雖然它不接受用*指定的區域長度,但仍是值得推薦的好東西)。

這些代碼片段,最好被封裝成函數。封裝的一個優點是,我們不需要每次使用時都計算最後一個區域的長度。下面的函數基本上等價于“解決方案”小節給出的直接使用struct.unpack的代碼片段:

一個值得注意(或者說值得批評)的設計是該函數提供了lastfield=false這樣一個可選參數。這基于一個經驗,雖然我們常常需要跳過最後的長度未知的部分,有時候我們還是需要擷取那段資料。采用lastfield and s or x(等同于c語言中的三元運算符,lastfield?"s":"c")這樣的表達式,我們省去了一個if/else,不過是否需要為這點緊湊犧牲可讀性還有值得商榷之處。參看第18.9節中有關在python中模拟三元運算符的内容。

若fields函數在一個循環内部被調用,使用元組(baseformat, len(theline), lastfield)作為key來充分利用memoizing機制将極大地提高性能。這裡給出一個使用memoizing機制的fields版本:

這種利用緩存的方法,目的是将比較耗時的格式準備工作一次完成,并存儲在_cache字典中。當然,正像所有的優化措施一樣,這種采用了緩存機制的優化也需要通過測試來确定究竟能在多大程度上提高性能。對這個例子,我的測試結果是,通過緩存優化的版本要比優化之前快約30%到40%,換句話說,如果這個函數不是你的程式的性能瓶頸部分,其實沒有什麼必要多此一舉。

“解決方案中”給出的另一個關于 lc的代碼片段,也可以封裝成函數:

對最後一個代碼片段的封裝:

在上面這些例子中,利用清單推導來切片要比用struct.unpack略好一些。

用生成器可以實作一個完全不同的方式,像這樣:

當需要循環周遊擷取的結果序列時,無論是顯式調用,還是借助一些可調用的“累加器”,比如’’.join來進行隐式調用,基于生成器的方式都會更加合适。如果需要的是各字段資料的清單,你手上得到的結果卻是一個生成器,可以調用内建的list來完成轉化,像這樣: