在Python中高效进行多个字符串替换
如果我想要进行多个字符串替换,最有效的方法是什么呢?
我在使用过程中遇到的一个例子是这样的:
>>> strings = ['a', 'list', 'of', 'strings']
>>> [s.replace('a', '')...replace('u', '') for s in strings if len(s) > 2]
['a', 'lst', 'of', 'strngs']
2 个回答
你可能会发现,创建一个正则表达式一次性做所有替换会更快。
另外,把替换的代码放到一个函数里也是个好主意,这样如果列表中有重复的内容,你可以使用记忆化来提高效率。
>>> import re
>>> [re.sub('[aeiou]','',s) for s in strings if len(s) > 2]
['a', 'lst', 'of', 'strngs']
>>> def replacer(s, memo={}):
... if s not in memo:
... memo[s] = re.sub('[aeiou]','',s)
... return memo[s]
...
>>> [replacer(s) for s in strings if len(s) > 2]
['a', 'lst', 'of', 'strngs']
你提到的具体例子(删除单个字符)非常适合使用字符串的 translate
方法,单个字符替换成单个字符也是一样。如果输入的字符串是Unicode格式的,那么除了前面提到的两种“替换”方式外,单个字符替换成多个字符的字符串也可以用 translate
方法来实现(不过如果你需要处理字节字符串就不行了)。
如果你需要替换多个字符的子字符串,我建议使用正则表达式——不过不是按照 @gnibbler 的回答那样;而是我会用 r'onestring|another|yetanother|orthis'
来构建正则表达式(用竖线把你想替换的子字符串连接起来——当然,如果它们包含特殊字符,一定要用 re.escape
处理一下),然后根据字典写一个简单的替换函数。
我现在不打算提供太多代码,因为我不知道你实际需要的是哪种情况,但(等我回家再看看 Stack Overflow;-) 如果需要的话,我会很乐意根据你对问题的修改来添加代码示例,这比对这个回答的评论要有用得多;-)。
编辑:在评论中,提问者说他想要一个“更通用”的答案(但没有说明这是什么意思),然后在他问题的编辑中说他想研究各种代码片段之间的“权衡”,而这些代码片段都是使用单字符子字符串(而且检查它们的存在,而不是像最初请求的那样进行替换——这完全是不同的意思,当然)。
鉴于这种完全的混乱,我只能说,要“检查权衡”(性能方面),我喜欢使用 python -mtimeit -s'setup things here' 'statements to check'
(确保要检查的语句没有副作用,以避免扭曲时间测量,因为 timeit
会隐式循环以提供准确的时间测量)。
一个通用的答案(没有任何权衡,涉及多个字符的子字符串,所以与他问题的编辑完全相反,但与他的评论一致——这两者完全矛盾,因此不可能同时满足):
import re
class Replacer(object):
def __init__(self, **replacements):
self.replacements = replacements
self.locator = re.compile('|'.join(re.escape(s) for s in replacements))
def _doreplace(self, mo):
return self.replacements[mo.group()]
def replace(self, s):
return self.locator.sub(self._doreplace, s)
示例用法:
r = Replacer(zap='zop', zip='zup')
print r.replace('allazapollezipzapzippopzip')
如果要替换的某些子字符串是Python关键字,它们需要以稍微不同的方式传入,例如,以下代码:
r = Replacer(abc='xyz', def='yyt', ghi='zzq')
会失败,因为 def
是一个关键字,所以你需要这样:
r = Replacer(abc='xyz', ghi='zzq', **{'def': 'yyt'})
或者类似的方式。
我觉得用类来处理这个问题是个不错的选择(而不是使用过程式编程),因为用于定位要替换的子字符串的正则表达式、表示要替换成什么的字典,以及执行替换的方法,确实需要“放在一起”,而类实例正好可以很好地实现这种“放在一起”的效果。在这种情况下,闭包工厂也可以工作(因为 replace
方法实际上是实例中唯一需要在“外部”可见的部分),但可能会不太清晰,调试起来也更困难:
def make_replacer(**replacements):
locator = re.compile('|'.join(re.escape(s) for s in replacements))
def _doreplace(mo):
return replacements[mo.group()]
def replace(s):
return locator.sub(_doreplace, s)
return replace
r = make_replacer(zap='zop', zip='zup')
print r('allazapollezipzapzippopzip')
唯一真正的优势可能是性能稍微好一点(需要用 timeit
在被认为重要且具有代表性的基准案例上进行检查),因为在这种情况下,访问“自由变量”(replacements
、locator
、_doreplace
)可能比访问合格名称(self.replacements
等)稍微快一点(这是否成立将取决于使用的Python实现,因此需要在重要基准上用 timeit
进行检查!)。