Подтвердить что ты не робот

Узнать количество групп захвата в регулярных выражениях Python

Есть ли способ определить, сколько групп захвата есть в данном регулярном выражении?

Я хотел бы иметь возможность делать следующее:

def groups(regexp, s):
    """ Returns the first result of re.findall, or an empty default

    >>> groups(r'(\d)(\d)(\d)', '123')
    ('1', '2', '3')
    >>> groups(r'(\d)(\d)(\d)', 'abc')
    ('', '', '')
    """
    import re
    m = re.search(regexp, s)
    if m:
        return m.groups()
    return ('',) * num_of_groups(regexp)

Это позволяет мне делать такие вещи, как:

first, last, phone = groups(r'(\w+) (\w+) ([\d\-]+)', 'John Doe 555-3456')

Однако я не знаю, как реализовать num_of_groups. (В настоящее время я просто работаю над этим.)

EDIT: Следуя рекомендациям от rslite, я заменил re.findall на re.search.

sre_parse кажется наиболее надежным и всеобъемлющим решением, но требует обхода дерева и кажется немного тяжелым.

Регулярное выражение MizardX похоже охватывает все базы, поэтому я собираюсь пойти с этим.

4b9b3361

Ответ 1

def num_groups(regex):
    return re.compile(regex).groups

Ответ 2

Свойство lastindex объекта соответствия должно быть тем, что вы ищете. См. re module docs.

Ответ 3

Возможно, что-то изнутри sre_parse может помочь.

На первый взгляд может быть что-то вроде:

>>> import sre_parse
>>> sre_parse.parse('(\d)\d(\d)')
[('subpattern', (1, [('in', [('category', 'category_digit')])])), 
('in', [('category', 'category_digit')]), 
('subpattern', (2, [('in', [('category', 'category_digit')])]))]

т.е. подсчитайте элементы типа "подшаблон":

import sre_parse

def count_patterns(regex):
    """
    >>> count_patterns('foo: \d')
    0
    >>> count_patterns('foo: (\d)')
    1
    >>> count_patterns('foo: (\d(\s))')
    1
    """
    parsed = sre_parse.parse(regex)
    return len([token for token in parsed if token[0] == 'subpattern'])

Обратите внимание, что мы учитываем только шаблоны корневого уровня здесь, поэтому последний пример возвращает только 1. Чтобы изменить это, токены должны искать рекурсивно.

Ответ 4

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

Для номера групп вы можете подсчитать количество открытых скобок '(' кроме тех, которые были экранированы '\'. Для этого можно использовать другое регулярное выражение:

def num_of_groups(regexp):
    rg = re.compile(r'(?<!\\)\(')
    return len(rg.findall(regexp))

Обратите внимание, что это не работает, если регулярное выражение содержит группы, не связанные с захватом, а также если '(' экранировано, используя его как '[(]'. Поэтому это не очень надежно. Но в зависимости от регулярных выражений, которые вы использование может помочь.

Ответ 5

Возможно, неправильно, но я не думаю, что есть способ найти число групп, которые были бы возвращены, если бы регулярное выражение соответствовало. Единственный способ, с помощью которого я могу сделать эту работу так, как вы хотите, это передать количество совпадений, которые ожидает ваше определенное регулярное выражение в качестве аргумента.

Чтобы прояснить это: когда findall преуспевает, вы хотите, чтобы только первое совпадение возвращалось, но когда он терпит неудачу, вам нужен список пустых строк? Поскольку комментарий, похоже, отображает все совпадения, возвращаемые в виде списка.

Ответ 6

Использование вашего кода в качестве основы:

def groups(regexp, s):
    """ Returns the first result of re.findall, or an empty default

    >>> groups(r'(\d)(\d)(\d)', '123')
    ('1', '2', '3')
    >>> groups(r'(\d)(\d)(\d)', 'abc')
    ('', '', '')
    """
    import re
    m = re.search(regexp, s)
    if m:
        return m.groups()
    return ('',) * len(m.groups())

Ответ 7

f_x = re.search(...)
len_groups = len(f_x.groups())