Python的file.truncate()意外未截断
我有一个非常简单的Python程序:
def print_file(filename):
with open(filename,'r') as read_file:
print(read_file.read())
def create_random_file(filename,count):
with open(filename,'w+', encoding='utf-8') as writefile:
for row_num in range(count):
writefile.write(f'{row_num}: fo bar baz\n')
def truncate_file_after_first_line(file,read_a_line):
file.seek(0,0) # go to start of file
print(f"After seeking to 0, at position {file.tell()}");
if (read_a_line):
header = file.readline()
print(f"After reading a line, at position {file.tell()}");
print(f"Found header '{header.rstrip()}'\n")
file.write('TRUNCATE AFTER THIS\n')
print(f"After writing marker, at position {file.tell()}");
file.truncate()
def mangle_file(filename,read_a_line):
with open(filename,'r+') as file:
truncate_file_after_first_line(file,read_a_line)
# ----
filename = 'testpy.txt'
read_a_line = True
create_random_file(filename,5)
print("Original file:")
print_file(filename)
mangle_file(filename,read_a_line)
print("Truncated file:")
print_file(filename)
所以,我:
- 创建了一个包含5行的文件(并且也打印到标准输出)。
- 然后,在
mangle_file()
函数里:- 我用
r+
选项打开文件,也就是说,可以读取和写入。文件指针在文件的开头。 - 根据布尔值
read_a_line
的不同,我会:- a) 移动到位置0,读取一行,写入标记
TRUNCATE AFTER THIS\n
,然后截断文件。 - b) 移动到位置0,写入标记
TRUNCATE AFTER THIS\n
,然后截断文件。
- a) 移动到位置0,读取一行,写入标记
- 最后,我关闭文件。
- 我用
- 然后再读取文件并打印出来。
听起来很简单,但在a)的情况下,当文件的第一行(即 0: fo bar baz
)在截断之前被读取时,结果文件是:
0: fo bar baz
1: fo bar baz
2: fo bar baz
3: fo bar baz
4: fo bar baz
TRUNCATE AFTER THIS
也就是说, truncate()
没有起作用,标记被添加到了未截断的文件中。而我本来期待在读取第一行后就截断:
0: fo bar baz
TRUNCATE AFTER THIS
对于b),结果文件如预期那样是:
TRUNCATE AFTER THIS
我对 truncate()
的理解有什么错误吗?
更新:添加了一些 tells
当 read_a_line = True
时:
Original file:
0: fo bar baz
1: fo bar baz
2: fo bar baz
3: fo bar baz
4: fo bar baz
After seeking to 0, at position 0
After reading a line, at position 14
Found header '0: fo bar baz'
After writing marker, at position 90
Truncated file:
0: fo bar baz
1: fo bar baz
2: fo bar baz
3: fo bar baz
4: fo bar baz
TRUNCATE AFTER THIS
当 read_a_line = False
时:
Original file:
0: fo bar baz
1: fo bar baz
2: fo bar baz
3: fo bar baz
4: fo bar baz
After seeking to 0, at position 0
After writing marker, at position 20
Truncated file:
TRUNCATE AFTER THIS
2 个回答
truncate()
这个函数确实可以截断文件,但它截断的位置不对。正如@defaultUsernameN提到的那个讨论串中所说,readline()
和truncate()
之间的互动似乎有一些没有记录的行为,导致它们对文件中“当前”所在位置的理解不一致。相关的问题仍然没有解决。
在你的代码中,你可以通过在截断之前手动调整位置来修复这个问题:
def truncate_file_after_first_line(file,read_a_line):
file.seek(0,0) # go to start of file
if (read_a_line):
header = file.readline()
print(f"Found header '{header.rstrip()}'\n")
file.seek(0,1) # go to second line
file.write('TRUNCATE AFTER THIS\n')
file.truncate()
输出:
Original file:
0: fo bar baz
1: fo bar baz
2: fo bar baz
3: fo bar baz
4: fo bar baz
Found header '0: fo bar baz'
Truncated file:
0: fo bar baz
TRUNCATE AFTER THIS
根据fopen的说明:
在读写流中,读和写可以随意交替进行。需要注意的是,ANSI C要求在输出和输入之间必须有一个文件定位的操作,除非输入操作遇到了文件末尾。(如果没有满足这个条件,那么读取的结果可能会包含除了最近一次写入之外的其他写入结果。)因此,在Linux下,最好在这样的流的写入和读取操作之间加上fseek(3)或fsetpos(3)操作。这种操作可能看起来没有任何作用(比如调用fseek(..., 0L, SEEK_CUR)只是为了它的同步效果)。
如果你把
file.write('TRUNCATE AFTER THIS\n')
改成
file.seek(file.tell())
file.write('TRUNCATE AFTER THIS\n')
这个函数应该会按照你想要的方式运行(在我的MacOS上是这样)。否则,“r+”模式可能会表现得像“ra”模式(尽管这并不是一个有效的模式)——也就是说,只能在文件的后面或最后追加内容(这取决于已经缓冲了多少文本)。