我正在研究一种特定类型文件的解析器,该文件被一些标题关键字分成几部分,后面跟着一堆异构数据。标题始终用空行分隔。以下内容:
Header_A
1 1.02345
2 2.97959
...
Header_B
1 5.1700 10.2500
2 5.0660 10.5000
...
每个标题包含非常不同类型的数据,并且根据块中的某些关键字,数据必须存储在不同的位置。我采用的一般方法是使用一些正则表达式捕获所有可以定义标题的关键字,然后遍历文件中的行。一旦找到匹配项,我会弹出行直到我到达一个空行,将所有数据存储在相应位置的行中。
这是代码的基本结构,其中“使用current_line做东西”将涉及一堆分支,具体取决于该行包含的内容:
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
一切正常但它相当于一个高度分支的结构,我发现它非常难以辨认,并且对于不熟悉代码的人来说可能难以理解。这也使得将行保持在<79个字符变得更加困难,而且通常不会感觉非常pythonic。
我正在研究的一件事是将每个标题的分支分成单独的函数。这有望提高可读性,但......
此外,由于各种原因,此代码必须能够在2.7中运行。
答案 0 :(得分:3)
您可以使用itertools.groupby根据您希望执行的处理功能对行进行分组:
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>)]
IT.groupby(f, key=header_func)
返回一个迭代器。迭代器产生的项目是2元组,例如
(<function __main__.process_a>, <itertools._grouper at 0xa0efecc>)
2元组中的第一项是header_func
返回的值。 2元组中的第二项是迭代器。这个迭代器产生来自f
的行,header_func(line)
都返回相同的值。
因此,IT.groupby
根据f
的返回值对header_func
中的行进行分组。当f
中的行是标题行 - Header_A
或Header_B
- 然后header_func
返回process_a
或process_b
时,函数我们希望用来处理后续行。
当f
中的行是标题行时,IT.groupby
返回的行组(2元组中的第二项)很短且无趣 - 它只是标题行。
我们需要在下一组中寻找有趣的线条。对于这些行,header_func
会返回None
。
所以我们需要看两个2元组:由IT.groupby
产生的第一个2元组给我们使用的函数,第二个2元组给出了应该应用头函数的行
一旦你将函数和迭代器都包含在有趣的行中,你只需调用func(lines)
就可以了!
请注意,扩展它以处理其他类型的标头非常容易。您只需要编写另一个process_*
函数,并在header_func
指示时修改process_*
以返回line
。
编辑:我之后删除了使用izip(*[iterator]*2)
它假设第一行是标题行。第一行可以是空白或非标题行,这会抛弃一切。我用一些if-statements
替换了它。它并不简洁,但结果更加稳健。
答案 1 :(得分:2)
如何将用于解析不同标头数据类型的逻辑拆分为单独的函数,然后使用字典从给定标头映射到正确的标题:
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)
此代码使用迭代,而不是索引来处理行。这样做的一个优点是除了在行列表上之外,您还可以直接在文件上运行它,因为文件是可迭代的。它还使边界检查变得非常容易,因为当迭代中没有任何内容时,以及当for
语句被命中时,break
循环将自动结束。
根据您正在对正在解析的数据执行的操作,您可能需要让各个解析器返回一些内容,而不是仅仅关闭并执行自己的操作。在这种情况下,您需要在顶级parse
函数中使用一些逻辑来获取结果并将其组合成一些有用的格式。也许字典最有意义,最后一行成为:
results_dict[header] = parser_for_header[header](iterator)
答案 2 :(得分:2)
您也可以使用生成器的send
功能执行此操作:)
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()
如果替换
,您可以从主循环中删除所有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)
与
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)