如何最好地原地修改文本文件?

8

我有一个文本文件(我们称之为“Potatoes.txt”),其中包含以下信息:

Town 1,300,
Town 2,205,
Town 3,600,
Town 4,910,
Town 5,360,

我想做的是减少某些城镇的数量,并相应地修改文本文件。经过一些研究,我发现无法直接修改文本文件,而我需要保持文本文件名称不变,只是里面的值不同,因此我目前正在尝试以下方法:

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)

基本上,我只是用一个空文件覆盖旧文件,然后将修改/临时文件中的值复制过来。我有没有漏掉更好的方法?


1
可能是以下问题的重复:Python:在特定字符后修改文本文件使用Python修改现有文本文件中的参数,还有其他很多(在此处搜索python modify text file)。 - Ken White
城镇在文件中的顺序是否重要?文件有多大(我们能否将其全部装入内存)? - the_constant
不要忘记在 with open("ModifedPotatoes.txt","r") as file: 中使用 w 而不是 r - Aidenhjj
4个回答

6
我进行了一些研究,似乎无法修改文本文件。
有一个模块可以在循环过程中给你与修改文本相同的效果。尝试使用fileinput模块,并将inplace选项设置为True
以下是一段Python3.6代码以供参考:
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)

我也遇到了这个模块,但使用它的优势是什么,相比Jeff-k提到的解决方案呢? - himanshu219
@himanshu219 其中一个优点是该过程是自动化的。另一个优点是fileinput会写入一个单独的文件,只有在写入成功后才会取消链接旧文件并执行重命名--这意味着如果在写入过程中出现问题,您的数据不会被覆盖。 - Raymond Hettinger
抓住了。是否可以使用 data = f.read() data= data.replace() f.write(data) 而不是逐行读取? - himanshu219

4

使用模式"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()

为了保持所有内容的整洁,我使用 seek(0) 在读取文件后将其倒回,从缓冲区中写回每一行,并在关闭之前截断文件的任何剩余部分。如果这些操作不必要,我会很感兴趣知道何时何地。
这种变化不会修改目录中的其他文件,在代码可能同时运行于不同输入文件的情况下是有益的。我不知道只打开一个文件一次是否有性能优势,但它可能确实有一定的优势。

我不知道性能如何,但在我看来,这个答案非常好。它保留了文件的编码,并且您可以使用newline=''和infile.newlines来保留原始的行结尾:https://docs.python.org/3/library/functions.html#open。只需确保在infile.seek(0)之前保存infile.newlines,这样可以避免奇怪地从infile中删除换行符。 - Samuel

1

尝试:

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]))

这绝对不是最好的方式,但它肯定更好并且有效。


1
你可以使用csv模块来进行文件/字符串处理。
只需读取所有的值并逐行循环,根据需要进行调整。然后使用csv.writer对象将它们写回到新文件中。
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')

这行代码

line[1] = str(int(line[1]) - 20)

这段代码可能有点混乱。这是因为csv中的值都是字符串。所以这是一个简单的方法,将其转换为整数,减去20并再次转换为字符串。

看着你的代码,里面有一个初学者经常犯的错误。

if "Town 2" or "Town 4" in line:

你必须意识到这是两个单独语句的组合,不是你期望的结果。第一个语句只是 Town 2,它总是会计算为 True。第二个语句是 "Town 4" in line",如果字符串 "Town 4" 包含在 line 字符串中,就会返回 True。

你的意图无疑是要测试这两个字符串是否在 line 中。为了做到这一点,你需要明确地测试这两个字符串。

if "Town 2" in line or "Town 4" in line:

会按预期工作。但是你可以更进一步,消除该语句中存在的一些不雅之处。

你知道该字符串应始终出现在字符串的第一个元素中,在 split 之后,这是你代码中的 info[0](或者在我的代码中,我让 csv 进行了拆分,所以是 line[0])。

因此,你可以写成:

if line[0] in ('Town 2', 'Town 4'):

我认为您会同意,这样更易于阅读,并且减少了重复输入,特别是如果您要添加更多的城镇。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接