一行获取捕获组

13 投票
9 回答
6256 浏览
提问于 2025-04-18 04:45

有一种常见的方法可以获取捕获组的值,如果没有匹配到,就返回一个空字符串:

match = re.search('regex', 'text')
if match:
    value = match.group(1)
else:
    value = ""

或者:

match = re.search('regex', 'text')
value = match.group(1) if match else ''

有没有简单又符合Python风格的方法可以在一行内做到这一点呢?

换句话说,我能不能为捕获组提供一个默认值,以防它没有找到?


举个例子,我需要从文本中提取所有字母数字字符(还有 _)在 key= 字符串之后的部分:

>>> import re
>>> PATTERN = re.compile('key=(\w+)')
>>> def find_text(text):
...     match = PATTERN.search(text)
...     return match.group(1) if match else ''
... 
>>> find_text('foo=bar,key=value,beer=pub')
'value'
>>> find_text('no match here')
''

那么 find_text() 能不能写成一行呢?

这只是一个例子,我在寻找一种通用的方法。

9 个回答

2

一行版本:

if re.findall(pattern,string): pass

这里的问题是,你想要准备处理多个匹配项,或者确保你的模式只匹配一次。扩展版本:

# matches is a list
matches = re.findall(pattern,string)

# condition on the list fails when list is empty
if matches:
    pass

那么对于你的例子“从 key= 字符串后提取所有字母数字字符(和 _)”:

# Returns 
def find_text(text):
    return re.findall("(?<=key=)[a-zA-Z0-9_]*",text)[0]
2

关于“有没有简单又符合Python风格的方法可以一行搞定?”答案是没有。如果你想在一行内实现这个功能(不定义自己的包装器),那么结果会比你已经展示的那些方法更难读懂。不过,定义自己的包装器完全符合Python风格的,使用两行清晰易读的代码也比用一行复杂难懂的代码要好。

关于Python 3.8+:新推出的“海象运算符”让你可以在不使用复杂技巧的情况下实现一行代码,具体可以参考PEP 572

value = match.group(1) if (match := re.search('regex', 'text')) else ''

很多人认为这符合Python风格,尤其是那些支持这个PEP的人。不过,也要注意到对此有激烈的反对意见。争论非常激烈,以至于Guido van Rossum在宣布接受这个PEP的第二天就辞去了Python的BDFL职务。

3

在一行代码里,你可以两次使用一个函数的结果:只需要创建一个叫做“lambda表达式”的东西,然后在参数中调用这个函数。

value = (lambda match: match.group(1) if match else '')(re.search(regex,text))

不过,我觉得这样写的代码不太容易理解。写代码的时候要负责任——如果你要写一些比较复杂的代码,记得加上说明性的注释!

6

你可以尝试一下这个模式,在字符串的捕获组末尾加一个空的选择项:

>>> re.search(r'((?<=key=)\w+|$)', 'foo=bar,key=value').group(1)
'value'
>>> re.search(r'((?<=key=)\w+|$)', 'no match here').group(1)
''
10

引用一下 MatchObjects 文档 的内容:

匹配对象的布尔值总是 True。因为 match()search() 在没有匹配时会返回 None,所以你可以用简单的 if 语句来检查是否有匹配:

match = re.search(pattern, string)
if match:
   process(match)

由于没有其他选择,而且你在使用一个函数,我想给你介绍一个替代方案:

def find_text(text, matches = lambda x: x.group(1) if x else ''):
    return matches(PATTERN.search(text))

assert find_text('foo=bar,key=value,beer=pub') == 'value'
assert find_text('no match here') == ''

这其实是完全一样的,只不过你需要检查的部分已经默认设置好了。

考虑到 @Kevin 的解决方案和 @devnull 在评论中的建议,你可以这样做:

def find_text(text):
    return next((item.group(1) for item in PATTERN.finditer(text)), "")

这个方法利用了 next 函数可以接受默认返回值作为参数的特点。不过,这样做在每次迭代时会增加创建生成器表达式的开销。所以,我建议还是用第一个版本。

撰写回答