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__ 的文档。