Как уже упоминалось в комментариях, правильный и простой способ сделать это - использовать стек:
my_str = "hanz(and(franz/bob())+ 7) + tom(2)"
stack = []
parens = []
for i, c in enumerate(my_str):
if c == "(":
stack.append(i)
elif c == ")":
parens.append((stack.pop(), i))
print(parens) # [(18, 19), (8, 20), (4, 24), (31, 33)]
Но если вы цените однострочники больше, чем соглашения по читабельности или кодированию, вы также можете втиснуть их всписок пониманий с побочными эффектами:
stack = []
parens = [(stack.pop(), i) for i, c in enumerate(my_str)
if c == "(" and stack.append(i) or c == ")"]
print(parens) # [(18, 19), (8, 20), (4, 24), (31, 33)]
При этом используется тот факт, что and
и or
оцениваются на короткое замыкание, поэтому он будет append
элементов только если c == "("
, но затемсбой, потому что append
возвращает None
и добавляет элементы к результатам, только если второе условие, c == ")"
истинно, выталкивает позицию самого последнего (
из стека.
Как минимум, это не полное злоупотребление списочным пониманием, поскольку результат не отбрасывается, а фактически желаемый результат, и, вероятно, его все еще легче понять, чем три списка, которые есть у у вас (хотя те работают без стороннихэффектов), но лучшее решение для «удобного» способа сделать это было бы: Сделайте его функцией, чем не имеет значения, сколько строк у него есть.