天天看點

《Python Cookbook(第2版)中文版》——1.10 過濾字元串中不屬于指定集合的字元

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

任務

給定一個需要保留的字元的集合,建構一個過濾函數,并可将其應用于任何字元串s,函數傳回一個s的拷貝,該拷貝隻包含指定字元集合中的元素。

解決方案

對于此類問題,string對象的translate方法是又快又好用的工具。不過,為了有效地使用translate來解決問題,事先我們必須做一些準備工作。傳遞給translate的第一個參數是一個翻譯表:在本節中,我們其實不需要什麼翻譯,是以我們必須準備一個特制的參數來指明“無須翻譯”。第二個參數指出了我們需要删除的字元:這個任務要求我們保留(正好反過來,不是删除)屬于某字元集合的字元,是以我們必須為該字元集合準備一個補集,作為第二個參數—這樣就可以删除所有我們不想保留的字元。閉包是一次性完成所有準備工作的最好方法,它能夠傳回一個滿足需求的快速過濾函數:

讨論

了解本節技巧的關鍵在于對python标準庫的string子產品的maketrans函數以及字元串對象的translate方法的了解。translate應用于一個字元串并傳回該字元串的一個拷貝,這個拷貝中的所有字元都将按照傳入的第一個參數(翻譯表)指定的替換方式來替換,而且,第二個參數指定的所有字元都将被删除。maketrans是建立翻譯表的一個工具函數。(翻譯表是一個正好有256個字元的字元串t:當你把t作為第一個參數傳遞給translate方法時,原字元串中的每一個字元c,在處理之後都被翻譯成了字元t[ord(c)]。)

在本節的技巧中,我們将整個過濾任務分解為準備階段和執行階段,使效能達到了最大化。由于包含所有字元的字元串需要重複使用,我們隻建立它一次,并在子產品導入後将它設定為全局變量。采用這種方式的原因是我們确定每個過濾函數都使用同樣的翻譯表,是以它非常節省記憶體。而我們要傳遞給translate的第二個參數—需要删除的字元,則依賴于需要保留的字元集合,因為它完全是後者的“補集”:我們需要通知translate删除我們不想保留的字元。是以,我們用makefilter工廠函數來建立需要删除的字元集合(字元串),即通過使用translate方法來删除“需要保留的字元”,這一步很快得以完成。和本節的其他函數一樣,無論是建立還是執行,translate的速度都非常快。程式中的執行部分給出的測試代碼,則展示了如何通過調用makefilter建構一個過濾函數,并給這個過濾函數綁定一個名字(隻需簡單地給makefilter的傳回結果指定個名字即可),然後對一些測試字元串調用該函數并列印出結果。

順帶一提,用allchars作為參數調用過濾函數會把所有需要保留的字元處理成一種非正常整的字元串—嚴格按照字母表排序而且沒有重複的字元。可以根據這種思路編寫一個很簡單的函數,将以字元串形式給出的字元集合處理成規整的形式:

在“解決方案”小節中給出的代碼,使用了def語句來建立一個嵌套的函數(閉包),這是因為def是最常見、最通用,也是最清晰的建立函數的語句。不過如果你樂意,也可以用lambda來代替原來的語句,隻需修改makefilter函數中的def和return語句,然後寫成隻需一行的return lambda語句:

大多數python玩家認為,相對于lambda,def更清晰且更具可讀性。

既然本節處理的一些字元串可以被看作字元集合,也可以用sets.set類型(或python 2.4中的内建set類型)來完成相同的任務。但得益于translate方法的威力和速度,在類似的這種直接處理字元串的任務中,使用translate總是比通過set來實作要快一些。不過,正如第1.8節提到的,本節中給出的技巧隻适用于普通字元串,對unicode字元串則不适用。

為了能夠解決unicode字元串的問題,我們需要做完全不同的準備工作。unicode字元串的translate方法隻需要一個參數:一個序列或者映射,并且根據字元串中的每個字元的碼值進行索引。碼值不是映射的鍵(或者序列的索引值)的字元會被直接複制,不做改變。與每個字元碼對應的值必須是一個unicode字元串(該字元的替換物)或者none(這意味着該字元需要被删除)。這種用法看上去既優雅又強大,可惜對于普通字元串卻不适用,是以我們還得重寫代碼。

通常,我們使用dict或list作為unicode字元串的translate方法的參數,來翻譯或者删除某些字元。但由于本節任務有些特殊(保留一些字元,删掉所有其餘字元),我們可能會需要一個非常龐大的dict或string—但僅僅是把所有的其他字元映射到none。更好的辦法是,編寫一個簡單的大緻實作了 _getitem _(進行索引操作時會調用的特殊方法)方法的類。一旦我們花點功夫完成了這個小類,我們可以讓這個類的執行個體可被調用,還可以直接給這個類起個makefilter的别名:

我們也可以直接就把這個類命名為makefilter,但是,基于傳統,一個類的名字通常應該首字母大寫;遵循傳統一般來說沒有什麼害處,是以,代碼就成了這個樣子。