读取基于位置的文本文件的正确方法
Proper way to read position based text file
所以我有一个包含这种(标准化)格式数据的文件:
12455WE READ THIS TOO796445 125997 554777
22455 888AND THIS TOO796445 125997 55477778 2 1
可能是因为cobol用的太多了。
每个字段都有固定的长度,我可以通过分割线来读取它。
我的问题是如何以更灵活的方式构建我的代码,而不是让我对切片使用硬编码偏移量?
我应该使用 class 之类的常量吗?
编辑:
同样,第一个数字(0->9 始终存在)决定了固定长度的行的结构。
此外,该文件由确保有效性的第 3 方提供,因此我不需要检查格式,只需要阅读它。
大约有 11 种不同的线结构。
创建一个 widths 的列表和一个接受这个和一个索引列号作为参数的例程。该例程可以通过添加所有先前的列宽来计算切片的起始偏移量,并添加索引列的宽度作为结束偏移量。
你可以有一个描述格式的列宽列表,然后像这样展开它:
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 在编辑的问题中提出的问题。这假设格式字符是一个整数,但它可以很容易地推广到其他格式指示符。
我的建议是使用以 5 位线路类型代码为关键字的字典。字典中的每个值都可以是字段偏移量列表(或(偏移量,宽度)元组),按字段位置索引。
如果您的字段有名称,使用 class 而不是列表来存储字段偏移数据 可能 会很方便。然而,namedtuples
在这里可能更好,因为那时你可以通过它的名称或它的字段位置访问你的字段偏移数据,这样你就可以两全其美。
namedtuple
s 实际上是作为 classes 实现的,但是定义一个新的 namedtuple
类型比创建一个明确的 class 定义要紧凑得多,并且 namedtuples
使用 __slots__
协议,因此它们比使用 __dict__
存储其属性的普通 class 占用更少的内存。
这是使用 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'
所以我有一个包含这种(标准化)格式数据的文件:
12455WE READ THIS TOO796445 125997 554777
22455 888AND THIS TOO796445 125997 55477778 2 1
可能是因为cobol用的太多了。
每个字段都有固定的长度,我可以通过分割线来读取它。
我的问题是如何以更灵活的方式构建我的代码,而不是让我对切片使用硬编码偏移量? 我应该使用 class 之类的常量吗?
编辑:
同样,第一个数字(0->9 始终存在)决定了固定长度的行的结构。 此外,该文件由确保有效性的第 3 方提供,因此我不需要检查格式,只需要阅读它。 大约有 11 种不同的线结构。
创建一个 widths 的列表和一个接受这个和一个索引列号作为参数的例程。该例程可以通过添加所有先前的列宽来计算切片的起始偏移量,并添加索引列的宽度作为结束偏移量。
你可以有一个描述格式的列宽列表,然后像这样展开它:
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 在编辑的问题中提出的问题。这假设格式字符是一个整数,但它可以很容易地推广到其他格式指示符。
我的建议是使用以 5 位线路类型代码为关键字的字典。字典中的每个值都可以是字段偏移量列表(或(偏移量,宽度)元组),按字段位置索引。
如果您的字段有名称,使用 class 而不是列表来存储字段偏移数据 可能 会很方便。然而,namedtuples
在这里可能更好,因为那时你可以通过它的名称或它的字段位置访问你的字段偏移数据,这样你就可以两全其美。
namedtuple
s 实际上是作为 classes 实现的,但是定义一个新的 namedtuple
类型比创建一个明确的 class 定义要紧凑得多,并且 namedtuples
使用 __slots__
协议,因此它们比使用 __dict__
存储其属性的普通 class 占用更少的内存。
这是使用 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'