Небольшой пример:
import re
pattern = re.compile(
r"(?P<hello>(?P<nested>hello)?(?P<other>cat)?)?(?P<world>world)?"
)
result = pattern.match("hellocat world")
print(result.groups())
print(result.groupdict() if result else "NO RESULT")
производит:
('hellocat', 'hello', 'cat', None)
{'hello': 'hellocat', 'nested': 'hello', 'other': 'cat', 'world': None}
Результат сопоставления регулярного выражения возвращает плоский словарь, а не словарь словарей, который соответствовал бы вложенной структуре шаблона регулярного выражения. Под этим я имею в виду:
{'hello': {'nested': 'hello', 'other': 'cat'}, 'world': None}
Существует ли «встроенный» (то есть что-то, включающее детали того, что предоставляется модулем re
) способ доступа к результату сопоставления, который сохраняет структуру вложенности регулярного выражения? Под этим я имею в виду, что следующее не является решением в контексте этого вопроса:
Поскольку вы не против использовать детали реализации модуля re
(которые в будущем могут быть изменены недокументированными), то, что вы хотите, становится возможным путем переопределения перехватчиков, которые вызываются, когда синтаксический анализатор входит и покидает группу захвата.
Читая исходный код Python-реализации синтаксического анализатора re
, мы можем обнаружить, что он вызывает метод opengroup объекта re._parser.State
при входе в группу захвата и вызывает метод closegroup при выходе.
Таким образом, мы можем добавить State
дополнительный атрибут стека диктов, представляющий поддерево текущей группы, переопределить opengroup
и closegroup
для построения поддеревьев при входе в группу и выходе из нее, а также предоставить метод nestedgroupdict
для заполнения листьев ( которые имеют пустые поддеревья) с фактическими совпадающими значениями из вывода метода groupdict
сопоставления:
import re
class State(re._parser.State):
def __init__(self):
super().__init__()
self.treestack = [{}]
def opengroup(self, name=None):
self.treestack[-1][name] = subtree = {}
self.treestack.append(subtree)
return super().opengroup(name)
def closegroup(self, gid, p):
self.treestack.pop()
super().closegroup(gid, p)
def nestedgroupdict(self, groupdict, _tree=None):
if _tree is None:
_tree, = self.treestack
result = {}
for name, subtree in _tree.items():
if subtree:
result[name] = self.nestedgroupdict(groupdict, subtree)
else:
result[name] = groupdict[name]
return result
re._parser.State = State
чтобы синтаксический анализатор создал состояние с treestack
, содержащее структуру именованных групп:
parsed = re._parser.parse(
r"(?P<hello>(?P<nested>hello)?(?P<other>cat)?)?(?P<world>world)?"
)
print(parsed.state.treestack)
который выводит:
[{'hello': {'nested': {}, 'other': {}}, 'world': {}}]
Затем мы можем скомпилировать проанализированный шаблон, чтобы сопоставить его со строкой, и вызвать groupdict
, чтобы получить сопоставление группового значения для передачи в наш nestedgroupdict
метод состояния для создания желаемой вложенной структуры:
groupdict = re._compiler.compile(parsed).match("hellocat world").groupdict()
print(parsed.state.nestedgroupdict(groupdict))
который выводит:
{'hello': {'nested': 'hello', 'other': 'cat'}, 'world': None}
Демо здесь
Это довольно круто. В конечном счете, я не уверен, что это долгосрочное решение, которое мне следует использовать, учитывая возможность будущих недокументированных изменений в функциях, используемых для создания этого, но, по крайней мере, это поможет мне продержаться, пока я не напишу что-то более надежное.
Нет ничего встроенного, что могло бы сделать это. Даже если группы захвата являются вложенными, они возвращаются последовательно.