Python食谱-1.10.使用一个字符集合来过滤字符串
| 原文作者: | Jurgen Hermann, Nick Perkins, Peter Cogolo |
|---|---|
| 中文编译: | Tony (digitalsatori) |
问题
给定一个字符集合,如何创建一个过滤功能,使其作用于任意的字符串s,返回一个字符串其只包含指定字符集合中的字符。
解决方法
字符串对象的translate方法是一个处理这类问题的快速,有效的方法。但是要让translate来完成这类任务,我们还需要事先做一些准备工作。translate的第一个参数是翻译表。在我们以上的问题中并不涉及任何翻译,所以我们必须让第一个参数表示“不翻译”。translate的第二个参数表示那些字符我们要删除。因为在上述问题中,我们被要求保留而不是删除指定的字符。所以我们必须要给予第二个参数所有非保留的字符,用以被删除。closure 是做这种预先准备工作的最佳选择:
import string
# 创建一个代表所有字符的可重用的字符串,其作用是
# 作为一个"不做任何翻译"的翻译对照表
allchars = string.maketrans('','')
def makefilter(keep):
"""返回一个函数,该函数以一个字符串为输入参数,运行
后返回一个仅包含'keep'中的字符的字符串拷贝。
注意:'keep' 必须是普通字符串, 不能是Unicode字符串
"""
#创建一个包含除'keep'中字符以外的所有字符的字符串:
#keep的反集,表示我们要删除的那些字符
delchars = allchars.translate(allchars, keep)
def thefilters(s):
return s.translate(allchars, delchars)
return thefilters
if __name__ == '__main__':
just_vowels = makefilter('aeiou')
print just_vowels('four score and seven years ago')
#结果: ouoeaeeyeaao
print just_vowels('tiger, tiger burning bright')
#结果: ieieuii
讨论
理解上面所示代码的关键在于了解string模块中的maketrans函数以及string对象的translate方法。一个字符串的translate方法返回这个字符串的一个副本。translate利用其第一个参数中获得的翻译对照表将原字符串对照替换,并且删除第二个参数中所指定的字符从而最终获得这个字符串副本。maketrans是专门用于创建翻译对照表的函数。(翻译对照表是一个包含恰好256个字符的字符串 t:当你将t作为第一个参数传递给某个字符串的translate方法时,这个字符串的每个字符 c 将被被转换为结果字符串的字符t[ord(c)]
在上述的解决方案中,通过将过滤任务分为准备和执行两个阶段,从而大大的提高了执行效率。代表所有字符的字符串无疑是可以被重用的,所以我们将其创建为该模块的全局变量。这样就可以保证让所有的过滤函数都使用同一个‘所有字符’字符串对象,避免浪费内存。作为translate方法的第二个参数,包含需删除字符的字符串对象,依赖于需要保存的字符集合,因为它是后者的反集:我们需要让translate删除所有那些不需要保留的字符。所以我们在 makefilter函数中创建了‘需删除字符'的字符串。创建上述字符串很容易,只要利用translate方法将‘所有字符’的字符串中删除’需保留字符‘即可。当我们将解决方案中的代码作为主程序运行时,其中的测试代码展示了如何通过调用makefilter函数来创建一个过滤函数,将这个过滤函数与一个变量通过赋值关联起来。然后对一些字符串调用该过滤函数并打印出结果。
顺便提一下。对'allchars(所有字符)'调用过滤函数,能获得’保留字符串'的规范形式,即获得一个按字母顺序排列,没有重复字符的’保留字符串‘的副本。你可以利用这个点子创建一个简单的函数获取任意字符串的‘规范形式。
def canonicform(s):
""" Given a string s, return s's characters as a canonic-form string:
alphabetized and without duplicates. """
return makefilter(s)(allchars)
在解决方法中的代码我们用的def语句定义一个函数并返回一个嵌套函数(closure).用def语句来定义函数很通用,清晰,当然你也可以用lambda来创建这个函数:
return lambda s: s.translate(allchars, delchars)
大多数的python程序员还是认为使用def比用lambda代码更清晰易读。
因为本问题针对的是对字符集合的处理,你也可以使用Python的集合(Set)类型来完成同样的任务。而translate方法的速度往往要比使用sets快得多。但是,正如 Python食谱1.8 中所提醒的,上文解决方案中的方法只能处理普通字符串,不能处理Unicode字符串。
要对Unicode字符串处理类似的问题,我们需要做一些完全不同的准备工作。Unicode字符串的translate方法只有一个形参:一个映射或者序列,其以字符串中每个字符的编码值为索引。字符的编码值未包含在映射的键值中(或者序列的索引值中)的,其将会原样拷贝至输出字符串,反之则以映射表或序列中的值替代,其值可以是一个Unicode字符也可以是None,这样原字符串中的这个对应字符就会被删除。这样的设置与普通字符串的处理有很大的不同,所以我们只能重新编码。
通常我们会使用一个字典或列表来作为Unicode字符串translate方法的参数。但是对于本文中涉及的任务(保留指定字符,删除所有其他字符),我们需要一个其大无比的dict或字符串,用来映射所有’非保留字符'为None。我们最好还是设计一个类来解决这个问题。我们会用到__getitem__特殊方法,该方法在对象做索引操作时被调用。我们还通过__call__方法使这个类的实例可被调用:
import sets class Keeper(object):
def _ _init_ _(self, keep):
self.keep = set(map(ord, keep))
def _ _getitem_ _(self, n):
if n not in self.keep:
return None
return unichr(n)
def _ _call_ _(self, s):
return unicode(s).translate(self)
makefilter = Keeper
if _ _name_ _ == '_ _main_ _':
just_vowels = makefilter('aeiouy')
print just_vowels(u'four score and seven years ago')
# emits: ouoeaeeyeaao
print just_vowels(u'tiger, tiger burning bright')
# emits: ieieuii
我们本可以将类命名为makefilter的,但是按约定,类名是要以大写字母开头的, 所以在这里我们遵守了这样的约定。
参见
Python食谱1.8 中关于字符串和Unicode对象的translate方法,以及在Python函数库参考及Python in a Nutshell中关于字符串模块的maketrans函数的介绍。













