最具python风格的分解高度分支解析器的方法

最具python风格的分解高度分支解析器的方法,python,loops,python-2.7,iterator,Python,Loops,Python 2.7,Iterator,我正在为一种特定类型的文件开发一个解析器,该文件被一些头关键字和一堆异构数据分解成多个部分。标题总是用空行分隔。大致如下: Header_A 1 1.02345 2 2.97959 ... Header_B 1 5.1700 10.2500 2 5.0660 10.5000 ... 每个标题包含非常不同类型的数据,根据块中的某些关键字,数据必须存储在不同的位置。我采用的一般方法是使用一些正则表达式来捕获可以定义头的所有关键字,然后遍历文件中的行。找到匹配项后,我弹出行,直

我正在为一种特定类型的文件开发一个解析器,该文件被一些头关键字和一堆异构数据分解成多个部分。标题总是用空行分隔。大致如下:

Header_A

1 1.02345
2 2.97959
...

Header_B

1   5.1700   10.2500
2   5.0660   10.5000
...
每个标题包含非常不同类型的数据,根据块中的某些关键字,数据必须存储在不同的位置。我采用的一般方法是使用一些正则表达式来捕获可以定义头的所有关键字,然后遍历文件中的行。找到匹配项后,我弹出行,直到找到一个空行,将行中的所有数据存储在适当的位置

这是代码的基本结构,“使用当前_行执行任务”将涉及一系列分支,具体取决于该行包含的内容:

headers = re.compile(r"""
    ((?P<header_a>Header_A)
    |
    (?P<header_b>Header_B))
    """, re.VERBOSE)

i = 0
while i < len(data_lines):
    match = header.match(data_lines[i])
    if match:
        if match.group('header_a'):
            data_lines.pop(i)
            data_lines.pop(i)

            #     not end of file         not blank line
            while i < len(data_lines) and data_lines[i].strip():
                current_line = data_lines.pop(i)
                # do stuff with current_line

        elif match.group('header_b'):
            data_lines.pop(i)
            data_lines.pop(i)

            while i < len(data_lines) and data_lines[i].strip():
                current_line = data_lines.pop(i)
                # do stuff with current_line
        else:
            i += 1
    else:
        i += 1
headers=re.compile(r”“”
((?PHeader_A)
|
(?PHeader_B))
“”,re.VERBOSE)
i=0
而i

一切都正常工作,但它相当于一个高度分支的结构,我发现它非常难以辨认,而且对于任何不熟悉代码的人来说都很难理解。这也使得在处保留行变得更加困难如何将用于解析不同头数据类型的逻辑拆分为单独的函数,然后使用字典从给定头映射到正确的头:

def parse_data_a(iterator):
    next(iterator) # throw away the blank line after the header
    for line in iterator:
        if not line.strip():
            break  # bale out if we find a blank line, another header is about to start
        # do stuff with each line here

# define similar functions to parse other blocks of data, e.g. parse_data_b()

# define a mapping from header strings to the functions that parse the following data
parser_for_header = {"Header_A": parse_data_a} # put other parsers in here too!

def parse(lines):
    iterator = iter(lines)
    for line in iterator:
        header = line.strip()
        if header in parser_for_header:
            parser_for_header[header](iterator)
这段代码使用迭代而不是索引来处理行。这样做的一个优点是,除了在行列表上运行它之外,还可以直接在文件上运行它,因为文件是可编辑的。它还使得边界检查非常容易,因为当iterable中没有剩余内容时,以及当命中
break
语句时,
for
循环将自动结束

根据您对正在解析的数据所做的操作,您可能需要让各个解析器返回一些内容,而不是只执行自己的操作。在这种情况下,您需要在顶级的
parse
函数中使用一些逻辑来获取结果并将其组装成一些有用的格式。也许一本字典最有意义,最后一行是:

results_dict[header] = parser_for_header[header](iterator)
您可以根据要执行的处理功能对行进行分组:

import itertools as IT

def process_a(lines):
    for line in lines:
        line = line.strip()
        if not line: continue        
        print('processing A: {}'.format(line))

def process_b(lines):
    for line in lines:
        line = line.strip()
        if not line: continue        
        print('processing B: {}'.format(line))

def header_func(line):
    if line.startswith('Header_A'):
        return process_a
    elif line.startswith('Header_B'):
        return process_b
    else: return None  # you could omit this, but it might be nice to be explicit

with open('data', 'r') as f:
    for key, lines in IT.groupby(f, key=header_func):
        if key is None:
            if func is not None:
                func(lines)
        else:
            func = key
应用于您发布的数据,上面的代码将打印出来

processing A: 1 1.02345
processing A: 2 2.97959
processing A: ...
processing B: 1   5.1700   10.2500
processing B: 2   5.0660   10.5000
processing B: ...

上面代码中最复杂的一行是

for key, lines in IT.groupby(f, key=header_func):
让我们试着将其分解为各个组成部分:

In [31]: f = open('data')

In [32]: list(IT.groupby(f, key=header_func))
Out[32]: 
[(<function __main__.process_a>, <itertools._grouper at 0xa0efecc>),
 (None, <itertools._grouper at 0xa0ef7cc>),
 (<function __main__.process_b>, <itertools._grouper at 0xa0eff0c>),
 (None, <itertools._grouper at 0xa0ef84c>)]
2元组中的第一项是
header\u func
返回的值。2元组中的第二项是迭代器。此迭代器从
f
生成行,其中
header\u func(line)
都返回相同的值

因此,
IT.groupby
根据
header\u func
的返回值对
f
中的行进行分组。当
f
中的行是标题行时--
header\u a
header\u B
--然后
header\u func
返回
process\u a
process\u B
,我们希望用于处理后续行的函数

f
中的行是标题行时,
IT.groupby
(二元组中的第二项)返回的行组很短且无趣,它只是标题行

我们需要在下一组中寻找有趣的台词。对于这些行,
header\u func
返回
None

所以我们需要看两个2元组:第一个2元组是由
IT.groupby
生成的,它给出了要使用的函数,第二个2元组给出了应该应用header函数的行

一旦有了函数和迭代器以及有趣的行,只需调用
func(lines)
就完成了

请注意,将其扩展到处理其他类型的头是非常容易的。您只需编写另一个
process*
函数,并在
指示返回
process*
时修改
header*


编辑:我删除了使用
izip(*[iterator]*2)

它假定第一行是标题行。第一行可以是空的,也可以是非标题行,这会使所有内容都消失。我将其替换为一些
if语句
。虽然没有那么简洁,但结果更可靠。

您也可以使用生成器的
发送功能来实现:)

如果更换,则可以从主循环中删除所有
if
条件

current_processer = None
for line in data_lines:
    line = line.strip()
    if line in header_processors:
        current_processor = header_processors[line]
        current_processor.send(None)
    elif line:
        current_processor.send(line)    


+1用于分解单独的函数。请注意,
data\u line.pop(i)
可能不是您想要的。它从
数据行
中删除
第i行
。一般来说,它不会删除
数据行
中的第一行。是的,非常正确,但代码绝对正确。这是一个老项目,我正在重新开始,我想重构/重组一些东西。谢谢你的详细解释!
data_lines = [
    'Header_A   ',
    '',
    '',
    '1 1.02345',
    '2 2.97959',
    '',
]

def process_header_a(line):
    while True:
        line = yield line
        # process line
        print 'A', line

header_processors = {
    'Header_A': process_header_a(None),
}

current_processer = None
for line in data_lines:
    line = line.strip()
    if line in header_processors:
        current_processor = header_processors[line]
        current_processor.send(None)
    elif line:
        current_processor.send(line)    

for processor in header_processors.values():
    processor.close()
current_processer = None
for line in data_lines:
    line = line.strip()
    if line in header_processors:
        current_processor = header_processors[line]
        current_processor.send(None)
    elif line:
        current_processor.send(line)    
map(next, header_processors.values())
current_processor = header_processors['Header_A']
for line in data_lines:
    line = line.strip()
    current_processor = header_processors.get(line, current_processor)
    line and line not in header_processors and current_processor.send(line)