在每行开头插入字符串

12

我如何在文本文件每一行的开头插入一个字符串?我有以下代码:

f = open('./ampo.txt', 'r+')
with open('./ampo.txt') as infile:
    for line in infile:
        f.insert(0, 'EDF ')
f.close

我遇到了以下错误:

'file' object has no attribute 'insert'
5个回答

28

Python内置“电池包括”

import fileinput
import sys

for line in fileinput.input(['./ampo.txt'], inplace=True):
    sys.stdout.write('EDF {l}'.format(l=line))

与已发布的解决方案不同,这种方法还保留了文件权限。


2
我不知道,这也保留了文件权限。+1 - eyquem
4
for循环逐行迭代读取ampo.txt中的内容。sys.stdout.writeprint几乎相同。我之所以选择使用sys.stdout.write而不是print,是因为print会添加额外的换行符,而sys.stdout.write则不会。由于指定了参数inplace=Truefileinput会将原始的ampo.txt文件移动到一个临时文件中,并将标准输出重定向到名为ampo.txt的新文件中。然后它复制权限并删除临时文件。还有更多选项,例如保留备份文件,请参见文档以获取更多信息。 - unutbu
@unutbu ,这个能不能改成只写入指定的行?比如第三行。 - dantdj
@dantdj:是的,您可以使用if fileinput.filelineno() == 3:根据您所在的行来更改行为。 - unutbu
@dantdj:要修改文件,您必须重写该文件的每一行。将文件视为内存块。要在中间插入一个字符,您必须编写该字符并将每个后续字符向右移动一个位置。按照相同的思路,您的程序应该像这样 - unutbu
显示剩余3条评论

8

你不能像那样原地修改文件。文件不支持插入。你必须将其全部读取然后再全部写出。

如果你愿意,可以逐行执行此操作。但在这种情况下,您需要写入临时文件,然后替换原始文件。因此,对于足够小的文件,仅一次性完成就更简单了,如下所示:

with open('./ampo.txt', 'r') as f:
    lines = f.readlines()
lines = ['EDF '+line for line in lines]
with open('./ampo.txt', 'w') as f:
    f.writelines(lines)

我建议使用惰性推导或者更好的方法是将字符串作为读取或写入操作的一部分。这样文件内容只会在内存中出现一次,而不是两次。 - Jan Hudec
1
[x for y in z]形式会创建一个新的列表对象,旧的列表对象在新列表构建之前无法进行垃圾回收,因此在某一时刻,原始字符串和修改后的字符串都同时存在于内存中。但是还有(x for y in z)形式,它创建了一个生成器。生成器是惰性的;它们一次只对一个输入值执行表达式并返回它,因此可以在下一个值被需要之前将其收集起来,它们永远不会同时存在于内存中。在这种情况下,您必须小心不要使其过于惰性,因为文件也支持惰性读取。 - Jan Hudec
1
这里有两种选择:['EDF ' + line for line in open('./ampo.txt', 'r')]使用生成器惰性读取文件,但在重写文件之前急切地评估列表以使整个文件保存在内存中。('EDF ' + line for line in f.readlines())使用急切的读取函数将文件读入内存,然后使用生成器急切地构造修改后的文本。当然,如果你两者都使用生成器,你会严重受伤,因为你会在把所有数据读出来之前就开始重写文件了。 - Jan Hudec
1
当然还有第三种选择,使用生成器来完成两个步骤,但是将数据写入新文件,然后将该文件重命名为原始文件。这样做的优点是可以处理大型文件(磁盘上通常比内存空间更多),并且如果进程被中断,不会丢失信息。 - Jan Hudec
@Jan,你的第三个选项已经在我的回答中提到了。 - David Heffernan
显示剩余2条评论

0

对于不太大的文件:

with open('./ampo.txt', 'rb+') as f:
    x = f.read()
    f.seek(0,0)
    f.writelines(('EDF ', x.replace('\n','\nEDF ')))
    f.truncate()

请注意,在理论上,这种情况下(内容已扩充),f.truncate() 实际上可能并不必要。因为 with 语句应该正确关闭文件,也就是在关闭之前写入 EOF(文件结束标志)。
这是我在示例中观察到的。但我很谨慎:我认为最好还是加上这个命令。当内容减少时,with 语句不会像之前的 EOF 那样在文件中正确地写入一个 EOF,因此文件中仍然存在一些尾随的初始字符。
所以如果 with 语句在内容减少时不写 EOF,那么当内容增加时为什么会写呢?
对于大文件,为了避免一次性将文件的所有内容都放入 RAM 中,请使用以下方法:
import os

def addsomething(filepath, ss):
    if filepath.rfind('.') > filepath.rfind(os.sep):
        a,_,c = filepath.rpartition('.')
        tempi = a + 'temp.' + c
    else:
        tempi = filepath + 'temp'

    with open(filepath, 'rb') as f, open(tempi,'wb') as g:
        g.writelines(ss + line for line in f)

    os.remove(filepath)
    os.rename(tempi,filepath)


addsomething('./ampo.txt','WZE')

请注意,这将会直接覆盖您的文件。此外,在开始写作之前,您必须确保自己知道要写的所有内容;在写作期间发生异常将会产生损坏的文件。(此答案存在该问题,因为Python是贪婪地评估代码。) - ninjagecko
@eyquem:f.truncate()的目的是什么? - unutbu
@ninjagecko 好主意。但是我不理解最后一句话的意思:什么是贪婪评估?(这不是Python)有什么后果吗?我看不出来重点在哪里。 - eyquem
@JanHudec:有哪些例子? - ninjagecko
@ninjagecko:两种生成器(生成器推导式和使用 yield 的函数)以及所有从它们的 __iter__() 方法返回一个生成器对象的东西(例如 file)。 - Jan Hudec
显示剩余3条评论

0
这里有一个解决方案,您可以将内容写入临时文件并将其移动到正确的位置。如果您要重写的文件非常大,则可能更喜欢此版本,因为它避免了像使用.read().readlines()这样的版本需要在内存中保留文件内容。此外,如果读取或写入时出现任何错误,您的原始文件将是安全的。
from shutil import move
from tempfile import NamedTemporaryFile

filename = './ampo.txt'
tmp = NamedTemporaryFile(delete=False)
with open(filename) as finput:
    with open(tmp.name, 'w') as ftmp:
        for line in finput:
            ftmp.write('EDF '+line)
move(tmp.name, filename)

-1
f = open('./ampo.txt', 'r')
lines = map(lambda l : 'EDF ' + l, f.readlines())
f.close()
f = open('./ampo.txt', 'w')
map(lambda l : f.write(l), lines)
f.close()

-1:你忘记了文件需要被写回的部分! - Jan Hudec
仍然是-1:在一个情况下使用map而应该使用推导式,在另一个情况下使用循环,这不是良好的Python风格。 - Jan Hudec

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