为什么re.findall返回一个元组列表,当我的模式只包含一个组?

16

假设有一个包含字母和两个分隔符 1 和 2 的字符串 s。我想按以下方式拆分字符串:

  • 如果子字符串 t 位于 1 和 2 之间,则返回 t
  • 否则,返回每个字符

因此,如果 s = 'ab1cd2efg1hij2k',则预期输出为 ['a', 'b', 'cd', 'e', 'f', 'g', 'hij', 'k']。

我尝试使用正则表达式:

import re
s = 'ab1cd2efg1hij2k'
re.findall( r'(1([a-z]+)2|[a-z])', s )

[('a', ''),
 ('b', ''),
 ('1cd2', 'cd'),
 ('e', ''),
 ('f', ''),
 ('g', ''),
 ('1hij2', 'hij'),
 ('k', '')]

我可以通过以下代码来获得答案:[ x[x[-1]!=''] for x in re.findall(r'(1([a-z]+)2|[a-z])', s) ],但是我仍然不理解输出结果。根据官方文档,如果正则表达式模式包含多个组,则findall函数返回一个元组列表。然而,我的模式只包含一个组。若有任何解释,请不吝赐教。

6个回答

8
您的模式有两个组,较大的组:
(1([a-z]+)2|[a-z])

还有第二个小一些的组,它是你第一个组的子集

([a-z]+)

这里有一个解决方案,可以给您期望的结果,但请注意,它非常丑陋,可能还有更好的方法。我只是想不出来:

import re
s = 'ab1cd2efg1hij2k'
a = re.findall( r'((?:1)([a-z]+)(?:2)|([a-z]))', s )
a = [tuple(j for j in i if j)[-1] for i in a]

>>> print a
['a', 'b', 'cd', 'e', 'f', 'g', 'hij', 'k']

1
你的模式相当奇怪。你不需要在 12 周围使用非捕获组,也不需要在整个模式周围使用分组(这会在输出中浪费大量的精力来跳过)。相反,只需接受 findall 调用将返回 2 元组,并使用 a = [x or y for x, y in a] 将它们转换为单个值即可。 - Blckknght

7

虽然我迟了5年才来参加这个活动,但我认为我可能已经找到了一个优雅的解决方案来处理 re.findall() 函数丑陋的元组输出以及多个捕获组。

通常情况下,如果你最终得到了像这样的输出:

[('pattern_1', '', ''), ('', 'pattern_2', ''), ('pattern_1', '', ''), ('', '', 'pattern_3')]

那么你可以使用这个小技巧将其转换为一个平面列表:

["".join(x) for x in re.findall(all_patterns, iterable)]

预期输出将如下所示:
['pattern_1', 'pattern_2', 'pattern_1', 'pattern_3']

这已经在Python 3.7上进行测试,希望它能有所帮助!


4
你的正则表达式有2个组,只需查看你使用的括号数量即可 :). 一个组将是([a-z]+),另一个将是(1([a-z]+)2|[a-z])。关键是你可以在其他组中放置组。因此,如果可能的话,你应该构建只有一个组的正则表达式,这样就不必后处理结果了。
只有一个组的正则表达式示例:
>>> import re
>>> s = 'ab1cd2efg1hij2k'
>>> re.findall('((?<=1)[a-z]+(?=2)|[a-z])', s)
['a', 'b', 'cd', 'e', 'f', 'g', 'hij', 'k']

4
如果您想要进行“或”匹配,而不需要将其拆分为匹配组,请在“或”匹配的开头添加“?:”。
没有“?:”
re.findall('(test (word1|word2))', 'test word1')

Output:
[('test word1', 'word1')]

使用'?:'

re.findall('(test (?:word1|word2))', 'test word1')

Output:
['test word1']

进一步解释:https://www.ocpsoft.org/tutorials/regular-expressions/or-in-regex/
或(or)在正则表达式中的使用方法。

这里的关键词是“非捕获组”...(仅为搜索引擎添加) - A. Rabus
救了我,谢谢。 - grantr

1

看一下类似问题的答案:https://bugs.python.org/issue6663 如果你使用findall,只需删除括号:

import re
s = 'ab1cd2efg1hij2k'
re.findall( r'(?<=1)[a-z]+(?=2)|[a-z]', s )

0
只需要进行一个简单的更改:将组更改为非捕获组 代码:
import re
s = 'ab1cd2efg1hij2k'
re.findall( r'(1(?:[a-z]+)2|[a-z])', s )

输出:

['a', 'b', '1cd2', 'e', 'f', 'g', '1hij2', 'k']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接