当你在Python代码中尝试生成正则表达式,一定会遇到这个问题。
(正确答案请直接看最下面黑体字→_→)
假设我们要将一段文本text中的某个关键词keyword替换为repl,因为某些原因不能使用str.replace而需要用到正则表达式,比如只替换keyword位于行首的情况,那可以用下面的代码:
re.sub('^' + keyword, repl, text)
多数时候它能正常工作:
>>> text = 'Result: 42'
>>> keyword = 'Result:'
>>> repl = 'The answer is'
>>> re.sub('^' + keyword, repl, text)
'The answer is 42'
但是当keyword稍微发生一点变化:
>>> text = '[Result] 42'
>>> keyword = '[Result]'
>>> repl = 'The answer is'
>>> re.sub('^' + keyword, repl, text)
'[Result] 42'
代码不工作了!
在这个简单例子里,很容易发现问题所在——在我们构造的正则表达式'^[Result]'
中,包含了特殊字符方括号,它们是Python正则表达式中的元字符(metacharacter),含义是匹配括号内的任意一个字符。所以'^[Result]'
可以匹配字符串'R...'
或者'e...'
,但不能匹配例子中的'[Result]...'
。
如何解决?简单,将元字符转义即可:
>>> text = '[Result] 42'
>>> keyword = '\[Result\]'
>>> repl = 'The answer is'
>>> re.sub('^' + keyword, repl, text)
'The answer is 42'
问题在于,keyword通常并非在代码中定义,而是在别处传入或者检测出的,所以我们需要代码自动检测字符串中的元字符,并在每个元字符前面插入转义符号'\'
。
一个能够完成这项工作的函数:
def escape_metachar(s):
METACHARS = r'.^$*+?{}[]\|()'
ret = ''
for i, c in enumerate(s):
if c in METACHARS:
ret += '\\'
ret += c
return ret
太丑了,为什么不用正则表达式来做呢。
def escape_metachar(s):
METACHARS = r'.^$*+?{}[]\|()'
meta_esc = re.sub('(?P.)', r'\\\g', METACHARS)
return re.sub('(?P[%s])' % meta_esc, r'\\\g', s)
re.sub
有个特殊语法,即'\g
可以引用正则表达式'(?P
中匹配到的pattern。上面的代码里,首先在METACHARS
中每个字符前面都加个转义,以便在之后的操作中能将其当做普通字符处理,然后在s
中查找这些字符,在其前面加转义。
由于第一步跟输入无关,而且METACHARS
的定义不太可能发生变化,为了效率起见,写成下面这样更好一点:
def escape_metachar(s):
METACHARS = r'\.\^\$\*\+\?\{\}\[\]\\\|\(\)'
return re.sub('(?P[%s])' % METACHARS, r'\\\g', s)
最后,希望你们不要像我一样,写完代码才发现有库函数re.escape
!
re.escape
!re.escape
!re.escape
!