Как указано в другом ответе, вы можете использовать библиотеку синтаксического анализа, такую как parsimonious
в сочетании с классом NodeVisitor
:
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
data = """
############################################
# Comments begin with '#'
############################################
[lj_pairs] # Section 1
indices: 0 2
# ID eps sigma
1 2.344 1.234 5
2 4.423 5.313 5
3 1.573 6.321 5
4 1.921 11.93 5
[bonds]
indices: 0 1
2 4.234e-03 11.2
6 -0.134545 5.7
"""
grammar = Grammar(
r"""
expr = (entry / garbage)+
entry = section garbage indices (valueline / garbage)*
section = lpar word rpar
indices = ws? "indices:" values+
garbage = ((comment / hs)* newline?)*
word = ~"\w+"
values = number+
valueline = values newline?
number = hs? ~"[-.e\d]+" hs?
lpar = "["
rpar = "]"
comment = ~"#.+"
ws = ~"\s*"
hs = ~"[\t\ ]*"
newline = ~"[\r\n]"
"""
)
tree = grammar.parse(data)
class DataVisitor(NodeVisitor):
def visit_number(self, node, visited_children):
""" Returns integer and float values. """
_, value, _ = visited_children
try:
number = int(value.text)
except ValueError:
number = float(value.text)
return number
def visit_section(self, node, visited_children):
""" Returns the section as text. """
_, section, _ = visited_children
return section.text
def visit_indices(self, node, visited_children):
""" Returns the index numbers. """
*_, values = visited_children
return values[0]
def visit_valueline(self, node, visited_children):
""" Returns every value from one line. """
values, _ = visited_children
return values
def visit_entry(self, node, visited_children):
""" Returns one entry (section, indices, values). """
section, _, indices, lst = visited_children
values = [item[0] for item in lst if item[0]]
return (section, {'indices': indices, 'values': values})
def visit_expr(self, node, visited_children):
""" Returns the whole structure as a dict. """
return dict([item[0] for item in visited_children if item[0]])
def visit_garbage(self, node, visited_children):
""" You know what this does. """
return None
def generic_visit(self, node, visited_children):
""" Returns the visited children (if any) or the node itself. """
return visited_children or node
d = DataVisitor()
result = d.visit(tree)
print(result)
Это даст
{
'lj_pairs': {'indices': [0, 2], 'values': [[1, 2.344, 1.234, 5], [2, 4.423, 5.313, 5], [3, 1.573, 6.321, 5], [4, 1.921, 11.93, 5]]},
'bonds': {'indices': [0, 1], 'values': [[2, 0.004234, 11.2], [6, -0.134545, 5.7]]}
}
Объяснение
Ваш исходный файл данных может рассматриваться как DSL
- d omain s pecific l anguage. Поэтому нам нужна грамматика, которая описывает, как ваш формат может выглядеть. Обычный способ здесь состоит в том, чтобы сначала сформулировать маленькие кубики, такие как пробел или «слово».
В
parsimonious
у нас есть несколько опций, одна из которых заключается в указании регулярных выражений (они начинаются с
~
):
ws = ~"\s*"
Здесь ws
обозначает \s*
, что равно нулю или более пробелов.
Другая возможность состоит в буквальном образовании части, такой как
lpar = "["
Последняя и самая мощная возможность состоит в том, чтобы
объединить обе эти меньшие части в одну большую, такую как
section = lpar word rpar
, что переводится как [word_characters_HERE123]
или аналогичная структура.
Теперь применяются обычные чередования (
/
) и квантификаторы, такие как
*
(ноль больше, жадный),
+
(на одну руду больше, жадный) и
?
(ноль или один, жадный) и могут быть ставить после каждого выражения, о котором мы можем подумать.
Если все работает нормально и грамматика подходит для данных, которые у нас есть, все разбирается на древовидную структуру, так называемый
a bstract
s yntax
t ree (AST). Для того, чтобы на самом деле сделать что-н. полезный с этой структурой (например, сделайте из этого хороший
dict
), нам нужно передать его в класс
NodeVisitor
. Это подвеска к нашей ранее сформированной грамматике, поскольку методы
visit_*
будут вызывать каждый лист, подходящий для нее. То есть метод
visit_section(...)
будет вызываться на каждом листе
section
с соответствующим
visited_children
.
Давайте сделаем это более понятным. Функция
def visit_section(self, node, visited_children):
""" Returns the section as text. """
_, section, _ = visited_children
return section.text
будет вызвано для section
части нашей грамматики (lpar section rpar
), поэтому лист section
имеет этих трех детей. Нас не интересует ни [
, ни ]
, а только сам текст раздела, поэтому мы распаковываем и возвращаем section.text
.
Нам нужно сделать это для каждого ранее определенного узла / листа. По умолчанию первое определение (в нашем случае expr
) и соответствующий visit_expr(...)
будут выходными данными класса NodeVisitor
, а все остальные узлы являются дочерними (внуки, правнуки и т. Д.) Этого узла.