1.16 替换字符串中的子串
任务
需要一个简单的方法来完成这样一个任务:给定一个字符串,通过查询一个替换字典,将字符串中被标记的子字符串替换掉。
解决方案
下面给出的解决办法既适用于Python 2.3,也适用于2.4:
def expand(format, d, marker='"', safe=False):
if safe:
def lookup(w): return d.get(w, w.join(marker*2))
else:
def lookup(w): return d[w]
parts = format.split(marker)
parts[1::2] = map(lookup, parts[1::2])
return ''.join(parts)
if _ _name_ _ == '_ _main_ _':
print expand('just "a" test', {'a': 'one'})
# 输出:just one test
如果参数safe是False,则默认条件下,字符串中所有被标记的子字符串必须能够在字典d中找到,否则,expand会抛出一个KeyError异常并终止执行。当参数safe被明确指定为True时,如果被标记的子字符串在字典中找不到,则被标记的部分也不会被改变。
讨论
expand函数代码的主体部分有个很有趣的地方:根据操作是否被要求为安全,它使用两个不同的嵌套函数(两者有着同样的名字lookup)中的一个。安全意味着被标记的子字符串应该能够在字典查到,如果查不到,不抛出KeyError异常。如果这个函数并不是必须安全的(默认情况下不安全),lookup根据索引访问字典d,并在该索引(子字符串)不存在的时候抛出个错误。但如果lookup被要求为安全的,它将使用d的方法get,get返回根据索引能够查到的值,若找不到就返回在两边加上了标记的被查询的子字符串。给safe传入True,表明你宁可看到输出中有标记符也不愿看到异常信息。marker+w+marker是可以替换w.join(marker*2)的另一种方式,但我采用后者的原因是,它展示了一种不太简明却很有意思的构造带引号字符串的方法。
不管用哪个版本的lookup,expand都会执行切分、修改、拼接—这些在Python的字符串处理中最重要的操作。在expand中进行修改的部分,使用了指定了步长的列表切片方法。确切地说,expand访问并重新绑定了parts的奇数索引的项,因为这些项正好是原字符串中位于两个标记符之间的部分。因此,它们就是被标记的子字符串,也就是需要在字典中查找的字符串。
本节解决方案给出的expand函数接受非常灵活的字符串语法形式,比基于$的string.Template更灵活。你如果想让输出字符串包括双引号,也可以指定其他的标记符。当然,函数也没有限制被标记的子串不能是标识符,可以轻松地插入Python表达式(d的 _getitem _方法会执行eval操作)或者任意其他占位符。而且,还可以轻易地搞出点有些不同的更有趣的效果,比如:
print expand('just "a" ""little"" test', {'a' : 'one', '' : '"'})
输出结果是just one "little" test。高级用户可以定制Python 2.4的string.Template类,通过继承来实现上述的所有功能,甚至更多其他高级功能。但本节解决方案中的小巧的expand函数却更加简洁易用。