在Python中高效进行多个字符串替换

8 投票
2 回答
7312 浏览
提问于 2025-04-16 02:02

如果我想要进行多个字符串替换,最有效的方法是什么呢?

我在使用过程中遇到的一个例子是这样的:

>>> strings = ['a', 'list', 'of', 'strings']
>>> [s.replace('a', '')...replace('u', '')  for s in strings if len(s) > 2]
['a', 'lst', 'of', 'strngs']

2 个回答

1

你可能会发现,创建一个正则表达式一次性做所有替换会更快。

另外,把替换的代码放到一个函数里也是个好主意,这样如果列表中有重复的内容,你可以使用记忆化来提高效率。

>>> 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']
12

你提到的具体例子(删除单个字符)非常适合使用字符串的 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 在被认为重要且具有代表性的基准案例上进行检查),因为在这种情况下,访问“自由变量”(replacementslocator_doreplace)可能比访问合格名称(self.replacements 等)稍微快一点(这是否成立将取决于使用的Python实现,因此需要在重要基准上用 timeit 进行检查!)。

撰写回答