Recipe 1.8. Checking Whether a String Contains a Set of Characters
Recipe 1.8. 檢測字串是否包含特定的字元集合
Credit: Jürgen Hermann, Horst Hansen
問題
您須要檢查字串中是否出現了特定的字元集合。
解法
最簡單的解法清晰、快捷、通用(不僅適用於字串,還適用於任何序列;不僅適用於集合,還適用於任何您可以對其進行 membership 測試的容器):
def containsAny(seq, aset):
""" Check whether sequence seq contains ANY of the items in aset. """
for c in seq:
if c in aset: return True
return False
採用更進階、更複雜的解法可換得一些速度優勢,利用標準庫模組 itertools 以基本相同的思路來處理:
import itertools
def containsAny(seq, aset):
for item in itertools.ifilter(aset.__contains__, seq):
return True
return False
討論
大多數與集合(set)相關的問題最好是用 Python 2.4 引入的內建 set 類型來解決(在 Python 2.3 中您可以使用等效的標準庫中的 sets.Set 類型)。但這其中也有例外。如下例所示,純粹基於 set 的方案可以是:
def containsAny(seq, aset):
return bool(set(aset).intersection(seq))
然而,此方案中的 seq 的全部項目都必須被檢查。而本條目“解法”欄目中的函數採用的是“短路(short-circuit)”手法:一旦找到就立刻返回。當然,若結果為 False ,“解法”欄目中的函數仍然必須檢查 seq 的全部項目——否則我們就無法確認 seq 中的每個項目都不在 aset 中。而在結果為 True 的情況下,我們經常能夠很快地明確結果,因為只須找到某一項是 aset 的成員即可。當然,上述情況是否值得斟酌,完全取決於資料的具體情況。若 seq 很短或者結果大多是 False ,那麼上述兩種方案就沒有實質上的區別;而對於很長的 seq 來說(通常可以很快地明確結果為 True),這區別就極為重要了。
“解法”欄目中 containsAny 的第一個版本的優點是簡單、明晰,以一目瞭然的方式表達了核心思路。第二個版本可能顯得“機巧”,而在 Python 世界中“機巧”並不是個褒義詞,因為 Python 世界的核心價值觀是簡單和明晰。然而,第二個版本還是值得斟酌,因為它展示了一種基於標準庫模組 itertools 的更進階的方案,而更進階的方案往往勝過較低級的方案(儘管在本條目中這一點頗具爭議)。itertools.ifilter 接收一個 predicate(謂詞)和一個 iterable(可迭代體),並將 iterable 中滿足 predicate 的項目 yield 出來。這裡將 anyset.__contains__ 作為 predicate ;當我們撰寫 in anyset 形式的語句來進行 membership 測試時,anyset.__contains__ 就是被綁定的方法(bound method),語句內部會調用它。因此,只要 seq 中有哪個項目是屬於 anyset ,ifilter 就會將其 yield 出來;一旦發生這種情況,我們就可以立刻返回 True 。如果代碼執行到了 for 語句之後,就必然意味著 return True 從未被執行過,因為 seq 的任何一個項目都不屬於 anyset ,由此應該 return False 。
---- BOX BEGIN ----
什麼是“Predicate(謂詞)”?
“Predicate(謂詞)”是討論編程時您會經常遇到的一個術語,意即“返回 True 或 False 的函數(或其他可調用對象)”。若 predicate 返回結果為真,就稱滿足了 predicate 。
---- BOX E N D ----
若您的應用程式需要諸如 containsAny 這樣的函數來檢查一個字串(或其他序列)是否包含某集合的成員,您可能還需要象下面這樣的變體形式:
def containsOnly(seq, aset):
""" Check whether sequence seq contains ONLY items in aset. """
for c in seq:
if c not in aset: return False
return True
containsOnly 與 containsAny 形式相同,只是在邏輯上反過來了。其他明顯類似的功能本質上要求檢查所有項目,無法應用“短路”手法,因此最好使用(Python 2.4 中)內建的 set 類型來處理(Python 2.3 中可使用 sets.Set ,用法相同):
def containsAll(seq, aset):
""" Check whether sequence seq contains ALL the items in aset. """
return not set(aset).difference(seq)
若您還沒用慣 set(或 sets.Set)的 difference 方法,請注意該方法的語義:對於任意集合 a ,a.difference(b) 返回 a 中所有不屬於 b 的元素的集合(如同 a-set(b))。例如:
>>> L1 = [1, 2, 3, 3]
>>> L2 = [1, 2, 3, 4]
>>> set(L1).difference(L2)
set([ ])
>>> set(L2).difference(L1)
set([4])
希望上述例子有助於理解如下事實:
>>> containsAll(L1, L2)
False
>>> containsAll(L2, L1)
True
(換句話說,請不要將 difference 與 set 的另一個方法 symmetric_difference 搞混淆了,symmetric_difference 返回 a 和 b 中所有“屬於 a 但不屬於 b,或者屬於 b 但不屬於 a”的元素的集合)
[譯註] 關於 symmetric_difference 請參考如下例子:
>>> L1 = [1, 2, 3, 5]
>>> L2 = [1, 3, 4, 8]
>>> set(L1).symmetric_difference(L2)
set([2, 4, 5, 8])
>>> set(L2).symmetric_difference(L1)
set([2, 4, 5, 8])
若您要處理的 seq 和 aset 只是(單純的,而非 Unicode)字串,可能就不完全需要本條目提供的函數所具有的通用性,不妨考慮採用 Recipe 1.10 中講到的更有針對性地方案(該方案基於字串的 translate 方法以及標準庫中的 string.maketrans 函數)。例如:
import string
notrans = string.maketrans('', '') # identity "translation"
def containsAny(astr, strset):
return len(strset) != len(strset.translate(notrans, astr))
def containsAll(astr, strset):
return not strset.translate(notrans, astr)
這個方案略顯巧妙,其原理在於:strset.translate(notrans, astr) 是由 strset 中那些“不屬於 astr 的字元”所組成的子序列。若這個子序列與 strset 長度相同,那就說明 strset.translate 並沒有移除 strset 中任何字元,因此也就說明在 strset 中沒有字元是屬於 astr 的。反之,若子序列為空白,那就說明 strset.translate 移除了 strset 中所有的字元,因此也就說明 strset 中所有的字元也都在 astr 中。當您想要將字串作為字元集合來對待時,自然而然地就會用到 translate 方法,因為該方法效率高,用起來順手,還具伸縮性(詳情請參見 Recipe 1.10)。
本條目的這兩組解決方案具有非常不同的通用性。前一組方案非常通用,並不僅限於字串處理,對操作對象的要求相當少。而後一組基於 tanslate 方法的方案僅在“astr 和 strset 都是字串”或“astr 和 strset 的功能在表象上與一般字元串非常接近”的情況下才能湊效。Unicode 字串不適用於基於 tanslate 方法的方案,因為 Unicode 字串的 translate 方法與一般字元串的 translate 方法的簽名式(signature)不一樣——Unicode 字串的 translate 方法只有一個參數(該參數為 dict 對象,其將代碼數字映射至 Unicode 字串或 None),而一般字元串的 translate 方法有兩個參數(都是字串)。
請參見
Recipe 1.10 ;Library Reference 和 Python in a Nutshell 中關於字串及 Unicode 對象的 translate 方法,以及 string 模組的 maketrans 函數的文檔;Library Reference 和 Python in a Nutshell 中關於內建 set 類型(僅限 Python 2.4 及後續版本)、sets 和 itertools 模組,以及特殊方法 __contains__ 的文檔。