As a first rewrite, I would come up with the following:
import contextlib
ID_WIDTH = 7
def process_file(input_filename, value_to_add):
"""Add number to first number on each line then write to another file"""
with contextlib.ExitStack as stack:
in_file = stack.enter_context(open(input_filename))
out_file = stack.enter_context(open(input_filename + '.out', 'w'))
out_file.write(next(in_file)) # Skip header
for line in in_file:
line_id = float(line[:ID_WIDTH]) + value_to_add
out_file.write('{:{width}.2f}{}'.format(line_id, line[ID_WIDTH:], width=ID_WIDTH))
if __name__ == '__main__':
filename = 'egtxt.prn'
value_to_add = 80.0
print('Processing', filename, '/ First number is', ID_WIDTH, 'characters wide / Adding', value_to_add, '...')
process_file(filename, value_to_add)
print('Done')
Important key-points being:
- usage of
contextlib.ExitStack
to properly close every files opened whatever happens
- usage of a function to encapsulate, test and reuse behaviour
- usage of docstrings instead of comments for added value
- usage of the iterator protocol on files to iterate line by line
- simplification of constants usage by using slicing or directly integrating them in other constants (
'.{sprec}f'
became '.2f'
for instance).
Now, specifying the width of the first field for parsing purposes feels terribly error-prone. You may cut an overly long ID and end up with weird/broken results. Instead, I would rely on the .split()
method of strings to extract out this first field. This is easy using the optional maxsplit
parameter:
>>> " spam \t eggs bacon foo bar baz spam ".split(maxsplit=1)
['spam', 'eggs bacon foo bar baz spam ']
You would just need to use the length of the field for formatting purposes but should add back the separator (a TAB I guess) between this field and the rest of the line.
As you also mentionned empty lines in the comments, you can test the length of the list returned by split
and decide whether you continue processing it or not. All in all, this may yield something like:
import contextlib
def process_file(input_filename, value_to_add, id_width=7, sep='\t'):
"""Add number to first number on each line then write to another file"""
with contextlib.ExitStack as stack:
in_file = stack.enter_context(open(input_filename))
out_file = stack.enter_context(open(input_filename + '.out', 'w'))
out_file.write(next(in_file)) # Skip header
for line in in_file:
try:
line_id, line = line.split(maxsplit=1)
line_id = float(line_id) + value_to_add
except ValueError:
continue
out_file.write('{:{width}.2f}{}{}'.format(line_id, sep, line, width=id_width))
if __name__ == '__main__':
filename = 'egtxt.prn'
value_to_add = 80.0
print('Processing', filename, 'Adding', value_to_add, '...')
process_file(filename, value_to_add)
print('Done')