Почему re.findall возвращает список кортежей, когда мой шаблон содержит только одну группу?

Скажем, у меня есть строка s, содержащая буквы и два разделителя 1 и 2. Я хочу разделить строку следующим образом:

  • если подстрока 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 возвращает список кортежей, если шаблон имеет более одного группа. Однако мой шаблон содержит только одну группу. Любое объяснение приветствуется.


person usual me    schedule 06.07.2014    source источник


Ответы (6)


У вашего шаблона есть две группы, большая группа:

(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']
person sshashank124    schedule 06.07.2014
comment
Ваш узор довольно странный. Вам не нужны незахватывающие группы вокруг 1 и 2 или группа вокруг всего шаблона (которую вы тратите кучу усилий, чтобы пропустить ее в выводе). Вместо этого просто примите, что вызов findall вернет 2 кортежа и превратит их в отдельные значения с помощью a = [x or y for x, y in a]. - person Blckknght; 06.07.2014

Ваше регулярное выражение имеет 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']
person moliware    schedule 06.07.2014

Я опоздал на вечеринку на 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. Надеюсь, поможет!

person Greem666    schedule 06.03.2019

Посмотрите на этот ответ на аналогичный вопрос: https://bugs.python.org/issue6663 Просто отпустите скобки, если вы используете findall:

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

Если вы хотите иметь совпадение «или» без разделения на группы совпадений, просто добавьте «?:» в начало совпадения «или».

Без '?:'

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/

person Sebastian N    schedule 04.03.2020
comment
ключевое слово здесь - группа без захвата... (добавлено исключительно для поисковой системы) - person A. Rabus; 03.12.2020

Просто нужно внести простое изменение: изменить группу на группу без захвата

Код:

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

Выход:

['a', 'b', '1cd2', 'e', 'f', 'g', '1hij2', 'k']
person K_one28    schedule 29.12.2020