Reputation: 257
I have a text file, (let's call it 'Potatoes.txt') containing the following info:
Town 1,300,
Town 2,205,
Town 3,600,
Town 4,910,
Town 5,360,
What I want to do is decrease the number for certain towns, and modify the text file accordingly. I did a little research and it appears you can't modify text files, and I need the text file to have the same name, just have different values inside it, so I'm currently doing this instead:
f = open("ModifiedPotatoes.txt","w")
f.close()
with open("Potatoes.txt","r") as file:
for line in file:
info = line.split(",")
if "Town 2" or "Town 4" in line:
info[1] -= 20
with open("ModifiedPotatoes.txt","a"):
infoStr = "\n" + ",".join(str(x) for x in info)
file.write(infoStr)
f = open("Potatoes.txt","w")
f.close()
with open("ModifedPotatoes.txt","r") as file:
for line in file:
with open("Potatoes.txt","a") as potatoesFile:
potatoesFile.write(line)
So basically I'm just overwriting the old file to a blank one, then copying the value from the modified/temporary file. Is there a better way to do this I'm missing?
Upvotes: 8
Views: 15966
Reputation: 191
It is possible to open a file for both reading and writing using mode "r+"
data = []
with open("temp", "r+") as inFile:
for line in inFile:
ar = line.split(",")
if ar[0] in ("Town 2", "Town 4"):
data.append( (ar[0], int(ar[1]) - 20, "\n") )
else:
data.append(ar)
inFile.seek(0)
for d in data:
inFile.write(",".join([str(x) for x in d]))
inFile.truncate()
In order to keep everything clean, I rewind the file after reading it using seek(0), write every line back into it from a buffer, and truncate any remaining part of the file before closing it. I would be interested to know if and when these operations aren't necessary.
This variation doesn't modify (clobber) any other files in the directory, which is a benefit in cases where the code might run simultaneously on different input files. I have no idea if only opening one file one time has any performance benefit, but it probably does to a small degree.
Upvotes: 4
Reputation: 226544
I did a little research and it appears you can't modify text files
There is a module that gives you the same effect as modifying text as you loop over it. Try using the fileinput module with the inplace option set to True.
Here is a little Python3.6 code to get you started:
from fileinput import FileInput
with FileInput(files=['Potatoes.txt'], inplace=True) as f:
for line in f:
line = line.rstrip()
info = line.split(",")
if "Town 2" in line or "Town 4" in line:
info[1] = int(info[1]) - 20
line = ",".join(str(x) for x in info))
print(line)
Upvotes: 6
Reputation: 21619
You can use the csv
module to do the file/string handling.
Just read all the values in and loop over them line by line, making adjustments as required. Then write them back to a new file using a csv.writer
object.
import csv
import shutil
import os
with open('potatoes.txt') as f, open('newpotatoes.txt', 'w') as fout:
rdr = csv.reader(f)
wrt = csv.writer(fout)
for line in rdr:
if line[0] in ('Town 2', 'Town 4'):
line[1] = str(int(line[1]) - 20)
wrt.writerow(line)
shutil.copyfile('newpotatoes.txt', 'potatoes.txt')
os.remove('newpotatoes.txt')
The line
line[1] = str(int(line[1]) - 20)
is possibly a little messy. It arises because the values from the csv are all strings. So this is a simple way to convert it to an integer, subtract 20 and convert back to a string.
Looking at your code, there is a mistake in there often made by beginners.
if "Town 2" or "Town 4" in line:
You have to realize that this is combination of two separate statements and is not what you expect. The first statement is just Town 2
, which will always evaluate to True
. The second statement is "Town 4" in line"
, which will return True if the string "Town 4" is contained anywhere in the line
string.
Your intent was no doubt to test if either string was in line
. To do that you need to explicitly test both strings.
if "Town 2" in line or "Town 4" in line:
Will work as expected. You can take it a step further though and cut out some inelegance present in that statement.
You know that string should always occur in the first element of the string, after the split
this is info[0]
in your code (or line[0]
in mine as I let csv
do the split).
You can therefore write
if line[0] in ('Town 2', 'Town 4'):
Which I think you'll agree is easier to read and less repetitive typing, especially if you go on to add more Towns.
Upvotes: 1
Reputation: 21183
Try:
mod_lines = []
with open("Potatoes.txt", "r") as f:
for line in f:
info = line.split(",")
if info[0] in ("Town 2", "Town 4"):
info[1] = int(info[1]) - 20
mod_lines.append(info)
with open("Potatoes.txt", "w") as f:
for m in mod_lines:
f.write(",".join([str(x) for x in m]))
This is certainly not the best way, but it's certainly better and works.
Upvotes: 1