正确的方法来读取基于位置的文本文件

时间:2015-11-16 09:19:06

标签: python parsing coda-format

所以我有一个包含这种(标准化)格式数据的文件:

 12455WE READ THIS             TOO796445 125997  554777     
 22455 888AND THIS       TOO796445 125997  55477778 2 1

可能是因为做了太多cobol的人所为。

每个字段都有一个固定的长度,我可以通过切割线来读取它。

我的问题是如何以一种使其更灵活的方式构建我的代码并且不会让我使用片段的硬编码偏移? 我应该使用一类类似的常量吗?

编辑:

第一个数字(始终存在0-> 9)确定具有固定长度的线的结构。 该文件也由确保有效性的第三方提供,因此我不需要检查格式只读它。 大约有11种不同的线结构。

3 个答案:

答案 0 :(得分:3)

我的建议是使用键入5位数线型代码的字典。字典中的每个值可以是字段偏移(或(偏移,宽度)元组)的列表,由字段位置索引。

如果您的字段有名称,可能方便使用类而不是列表来存储字段偏移数据。但是,namedtuples在这里可能会更好,因为您可以通过其名称或字段位置访问您的字段偏移数据,这样您就可以获得两全其美的效果。

namedtuple实际上是作为类实现的,但定义新的namedtuple类型比创建显式类定义要紧凑得多,namedtuples使用__slots__协议,因此它们占用的RAM少于使用__dict__来存储其属性的普通类。

这是使用namedtuples存储字段偏移数据的一种方法。我没有声称以下代码是最好的方法,但它应该给你一些想法。

from collections import namedtuple

#Create a namedtuple, `Fields`, containing all field names
fieldnames = [
    'record_type', 
    'special',
    'communication',
    'id_number',
    'transaction_code',
    'amount',
    'other',
]

Fields = namedtuple('Fields', fieldnames)

#Some fake test data
data = [
    #          1         2         3         4         5
    #012345678901234567890123456789012345678901234567890123
    "12455WE READ THIS             TOO796445 125997  554777",
    "22455 888AND THIS       TOO796445 125997  55477778 2 1",
]

#A dict to store the field (offset, width) data for each field in a record,
#keyed by record type, which is always stored at (0, 5)
offsets = {}

#Some fake record structures
offsets['12455'] = Fields(
    record_type=(0, 5), 
    special=None,
    communication=(5, 28),
    id_number=(33, 6),
    transaction_code=(40, 6),
    amount=(48, 6),
    other=None)

offsets['22455'] = Fields( 
    record_type=(0, 5),
    special=(6, 3),
    communication=(9, 18),
    id_number=(27, 6),
    transaction_code=(34, 6),
    amount=(42, 8),
    other=(51,3))

#Test.
for row in data:
    print row
    #Get record type
    rt = row[:5]
    #Get field structure
    fields = offsets[rt]
    for name in fieldnames:
        #Get field offset data by field name
        t = getattr(fields, name)
        if t is not None:
            start, flen = t
            stop = start + flen
            data = row[start : stop]            
            print "%-16s ... %r" % (name, data)
    print

<强>输出

12455WE READ THIS             TOO796445 125997  554777
record_type      ... '12455'
communication    ... 'WE READ THIS             TOO'
id_number        ... '796445'
transaction_code ... '125997'
amount           ... '554777'

22455 888AND THIS       TOO796445 125997  55477778 2 1
record_type      ... '22455'
special          ... '888'
communication    ... 'AND THIS       TOO'
id_number        ... '796445'
transaction_code ... '125997'
amount           ... '55477778'
other            ... '2 1'

答案 1 :(得分:1)

创建一个 widths 列表和一个接受此列表的例程以及一个索引列号作为参数。例程可以通过添加所有先前的列宽度来计算切片的起始偏移量,并为结束偏移量添加索引列的宽度。

答案 2 :(得分:1)

您可以拥有描述格式的列的宽度列表,并将其展开,如下所示:

formats = [
    [1, ],
    [1, 4, 28, 7, 7, 7],
]

def unfold(line):
    lengths = formats[int(line[0])]
    ends = [sum(lengths[0:n+1]) for n in range(len(lengths))]
    return [line[s:e] for s,e in zip([0] + ends[:-1], ends)]

lines = [
    "12455WE READ THIS             TOO796445 125997 554777",
]

for line in lines:
    print unfold(line)

修改:更新了代码,以便更好地匹配编辑过的问题中提出的maazza。这假设格式字符是一个整数,但它可以很容易地推广到其他格式指示符。