Разбор вложенных круглых скобок в python, захват содержимого по уровню

По-видимому, эта проблема возникает довольно часто после прочтения

Регулярное выражение для обнаружения циклов С++ и для циклов while и while с запятой и т.д.

и некоторое время думая о проблеме, я написал функцию для возврата содержимого, содержащегося внутри произвольного числа вложенных()

Функция может быть легко распространена на любой объект регулярного выражения, размещая здесь свои мысли и соображения.

любые рекомендации по рефакторингу будут оценены

(заметьте, я еще новичок в python, и мне не хотелось выяснять, как поднимать исключения или что-то еще, поэтому я просто получил функцию return 'fail', если он не мог понять, что происходит )

Отредактированная функция для учета комментариев:

def ParseNestedParen(string, level):
    """
    Return string contained in nested (), indexing i = level
    """
    CountLeft = len(re.findall("\(", string))
    CountRight = len(re.findall("\)", string))
    if CountLeft == CountRight:
        LeftRightIndex = [x for x in zip(
        [Left.start()+1 for Left in re.finditer('\(', string)], 
        reversed([Right.start() for Right in re.finditer('\)', string)]))]

    elif CountLeft > CountRight:
        return ParseNestedParen(string + ')', level)

    elif CountLeft < CountRight:
        return ParseNestedParen('(' + string, level)

    return string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]

Ответ 1

Вы не уточняете, что такое спецификация вашей функции, но это поведение кажется мне неправильным:

>>> ParseNestedParen('(a)(b)(c)', 0)
['a)(b)(c']
>>> nested_paren.ParseNestedParen('(a)(b)(c)', 1)
['b']
>>> nested_paren.ParseNestedParen('(a)(b)(c)', 2)
['']

Другие комментарии к вашему коду:

  • Docstring говорит "generate", но функция возвращает список, а не генератор.
  • Поскольку возвращается только одна строка, зачем возвращать ее в список?
  • При каких обстоятельствах функция возвращает строку fail?
  • Повторяя вызов re.findall, а затем отбрасывая результат, расточительно.
  • Вы пытаетесь перебалансировать круглые скобки в строке, но вы делаете это только по одной скобке за раз:
>>> ParseNestedParen(')' * 1000, 1)
RuntimeError: maximum recursion depth exceeded while calling a Python object

Как сказал Томи в вопросе который вы связали с, "регулярные выражения действительно являются неправильным инструментом для работы!"


Обычный способ разбора вложенных выражений состоит в использовании стека в следующих строках:

def parenthetic_contents(string):
    """Generate parenthesized contents in string as pairs (level, contents)."""
    stack = []
    for i, c in enumerate(string):
        if c == '(':
            stack.append(i)
        elif c == ')' and stack:
            start = stack.pop()
            yield (len(stack), string[start + 1: i])

>>> list(parenthetic_contents('(a(b(c)(d)e)(f)g)'))
[(2, 'c'), (2, 'd'), (1, 'b(c)(d)e'), (1, 'f'), (0, 'a(b(c)(d)e)(f)g')]

Ответ 2

Для совпадения в скобках требуется синтаксический анализатор с автоматом. Некоторые библиотеки существуют, но правила достаточно просты, чтобы мы могли написать их с нуля:

def push(obj, l, depth):
    while depth:
        l = l[-1]
        depth -= 1

    l.append(obj)

def parse_parentheses(s):
    groups = []
    depth = 0

    try:
        for char in s:
            if char == '(':
                push([], groups, depth)
                depth += 1
            elif char == ')':
                depth -= 1
            else:
                push(char, groups, depth)
    except IndexError:
        raise ValueError('Parentheses mismatch')

    if depth > 0:
        raise ValueError('Parentheses mismatch')
    else:
        return groups

print(parse_parentheses('a(b(cd)f)')) # ['a', ['b', ['c', 'd'], 'f']]

Ответ 3

#!/usr/bin/env python
import re

def ParseNestedParen(string, level):
    """
    Generate strings contained in nested (), indexing i = level
    """
    if len(re.findall("\(", string)) == len(re.findall("\)", string)):
        LeftRightIndex = [x for x in zip(
        [Left.start()+1 for Left in re.finditer('\(', string)], 
        reversed([Right.start() for Right in re.finditer('\)', string)]))]

    elif len(re.findall("\(", string)) > len(re.findall("\)", string)):
        return ParseNestedParen(string + ')', level)

    elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
        return ParseNestedParen('(' + string, level)

    else:
        return 'fail'

    return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]]

Тесты:

if __name__ == '__main__':

    teststring = "outer(first(second(third)second)first)outer"

    print(ParseNestedParen(teststring, 0))
    print(ParseNestedParen(teststring, 1))
    print(ParseNestedParen(teststring, 2))

    teststring_2 = "outer(first(second(third)second)"

    print(ParseNestedParen(teststring_2, 0))
    print(ParseNestedParen(teststring_2, 1))
    print(ParseNestedParen(teststring_2, 2))

    teststring_3 = "second(third)second)first)outer"

    print(ParseNestedParen(teststring_3, 0))
    print(ParseNestedParen(teststring_3, 1))
    print(ParseNestedParen(teststring_3, 2))

выход:

Running tool: python3.1

['first(second(third)second)first']
['second(third)second']
['third']
['first(second(third)second)']
['second(third)second']
['third']
['(second(third)second)first']
['second(third)second']
['third']
>>>