使用Python标准库struct https://docs.python.org/3/library/struct.html#module-struct模块将相当简单且相当快,因为它是用 C 编写的。下面的代码是如何使用它的。它还允许通过为字段中的字符数指定负值来跳过字符列。
import struct
fieldwidths = (2, -10, 24)
fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's') for fw in fieldwidths)
# Convert Unicode input to bytes and the result back to Unicode string.
unpack = struct.Struct(fmtstring).unpack_from # Alias.
parse = lambda line: tuple(s.decode() for s in unpack(line.encode()))
print('fmtstring: {!r}, record size: {} chars'.format(fmtstring, struct.calcsize(fmtstring)))
line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fields = parse(line)
print('fields: {}'.format(fields))
Output:
fmtstring: '2s 10x 24s', recsize: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')
正如您正在考虑的那样,这是一种使用字符串切片来实现此目的的方法,但担心它可能会变得太难看。它is有点复杂,速度也快,与基于的版本大致相同struct
模块——尽管我知道如何加快它的速度(这可能会让额外的复杂性变得值得)。请参阅下面有关该主题的更新。
from itertools import zip_longest
from itertools import accumulate
def make_parser(fieldwidths):
cuts = tuple(cut for cut in accumulate(abs(fw) for fw in fieldwidths))
pads = tuple(fw < 0 for fw in fieldwidths) # bool values for padding fields
flds = tuple(zip_longest(pads, (0,)+cuts, cuts))[:-1] # ignore final one
parse = lambda line: tuple(line[i:j] for pad, i, j in flds if not pad)
# Optional informational function attributes.
parse.size = sum(abs(fw) for fw in fieldwidths)
parse.fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
for fw in fieldwidths)
return parse
line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fieldwidths = (2, -10, 24) # negative widths represent ignored padding fields
parse = make_parser(fieldwidths)
fields = parse(line)
print('format: {!r}, rec size: {} chars'.format(parse.fmtstring, parse.size))
print('fields: {}'.format(fields))
Output:
format: '2s 10x 24s', rec size: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')
Update
正如我所怀疑的,有is一种使代码的字符串切片版本更快的方法——在 Python 2.7 中,它与使用的版本的速度大致相同struct
,但在 Python 3.x 中使其速度提高 233%(以及其自身的未优化版本,其速度与struct
版本)。
上面介绍的版本所做的是定义一个 lambda 函数,该函数主要是在运行时生成一堆切片的限制的推导式。
parse = lambda line: tuple(line[i:j] for pad, i, j in flds if not pad)
这相当于下面的语句,具体取决于值i
and j
in the for
循环,看起来像这样:
parse = lambda line: tuple(line[0:2], line[12:36], line[36:51], ...)
然而,后者的执行速度是后者的两倍多,因为切片边界都是常量。
幸运的是,使用内置函数将前者转换和“编译”为后者相对容易eval()
功能:
def make_parser(fieldwidths):
cuts = tuple(cut for cut in accumulate(abs(fw) for fw in fieldwidths))
pads = tuple(fw < 0 for fw in fieldwidths) # bool flags for padding fields
flds = tuple(zip_longest(pads, (0,)+cuts, cuts))[:-1] # ignore final one
slcs = ', '.join('line[{}:{}]'.format(i, j) for pad, i, j in flds if not pad)
parse = eval('lambda line: ({})\n'.format(slcs)) # Create and compile source code.
# Optional informational function attributes.
parse.size = sum(abs(fw) for fw in fieldwidths)
parse.fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
for fw in fieldwidths)
return parse