如何按行号从文件中读取特定行?

287

我正在使用 for 循环读取文件,但我只想读取特定的行,比如第 #26 行和 #30 行。有没有内置的功能可以实现这个要求呢?


1
可能重复:https://dev59.com/4HRB5IYBdhLWcg3wcm6d - Adam Matan
30个回答

333

如果要读取的文件很大,而且您不想一次性将整个文件读入内存:

fp = open("file")
for i, line in enumerate(fp):
    if i == 25:
        # 26th line
    elif i == 29:
        # 30th line
    elif i > 29:
        break
fp.close()

请注意,第 n 行的 i == n-1


在 Python 2.6 或更高版本中:

with open("file") as fp:
    for i, line in enumerate(fp):
        if i == 25:
            # 26th line
        elif i == 29:
            # 30th line
        elif i > 29:
            break

3
如果整个文件没有像linecache那样加载到内存中,则比我的解决方案更好。您确定enumerate(fp)不会这样做吗? - Adam Matan
17
enumerate(x) 使用 x.next,因此它不需要将整个文件存储在内存中。 - Alok Singhal
3
我对此有一点不满意,原因是A)你想使用“with”而不是打开/关闭配对,从而使代码体积更小;B)但实际上代码也并不是很短。这似乎是在速度/空间与Pythonic之间做出权衡。我不确定最佳解决方案是什么。 - Hamish Grubijan
7
使用with语句有些被过分强调,Python在过去的13年中没有使用它也很好。 - Dan D.
69
电力被高估了,人类在两十万年的时间里没有它也生活得很好。;-) "with"使它更安全、更易读,并缩短了一行代码。 - Romain Vincent
显示剩余4条评论

200

简短回答:

f=open('filename')
lines=f.readlines()
print lines[25]
print lines[29]
或者:
lines=[25, 29]
i=0
f=open('filename')
for line in f:
    if i in lines:
        print i
    i+=1

有更优雅的解决方案来提取多行文本:linecache(来源于"python: how to jump to a particular line in a huge text file?",一个之前的stackoverflow.com问题)。

引用上面链接到的Python文档:

>>> import linecache
>>> linecache.getline('/etc/passwd', 4)
'sys:x:3:3:sys:/dev:/bin/sh\n'

4更改为所需的行号即可。请注意,由于计数从零开始,因此4会将第五行作为结果。

如果文件可能非常大,并且读入内存时可能会出现问题,建议采用@Alok的建议并使用enumerate()

总结:

  • 对于小文件,可以使用fileobject.readlines()for line in fileobject 作为快速解决方案。
  • 对于阅读多个文件的情况,可以使用linecache实现更加优雅的解决方案,并且速度相当快。
  • 对于可能太大而无法放入内存的文件,请采用@Alok的建议并使用enumerate()。请注意,由于该方法是按顺序读取文件的,因此可能会变慢。

7
好的。我刚刚查看了linecache模块的源代码,看起来它会将整个文件读入内存中。因此,如果随机访问比尺寸优化更重要,那么使用linecache是最好的方法。 - Alok Singhal
7
使用 linecache.getline('some_file', 4) 可以获取第四行,而不是第五行。请注意保持原意不变,使翻译内容通俗易懂。 - Juan
有趣的事实:如果在第二个例子中使用集合(set)而不是列表(list),你将获得O(1)的运行时间。在列表中查找的时间复杂度为O(n)。内部,集合被表示为哈希表,这就是为什么你会获得O(1)的运行时间。在这个例子中并没有太大的影响,但如果使用大型数字列表并且关心效率,则应该使用集合。 - rady
linecache 现在似乎只适用于 Python 源文件。 - Paul H
你也可以使用 linecache.getlines('/etc/passwd')[0:4] 来读取第一、二、三和四行。 - zyy

42

为了提供另一种解决方案:

import linecache
linecache.getline('Sample.txt', Number_of_Line)

希望这个能够快速且容易 :)


1
希望这是最优解决方案。 - maniac_user
7
这将整个文件都读入内存。你可以直接调用file.read().split('\n'),然后使用数组索引查找所需的行。 - duhaime
你能给一个例子吗,@duhaime? - anon
1
@anon ''.join(file.readlines()).split('\n'))[5:10]` 给出了第6到10行的例子。不建议使用,因为它会将整个文件读入内存。 - questionto42
这是一个例子,对我来说它起作用了:def get_version(): versionLine = linecache.getline('config.php', 4) version = versionLine[19:24] return version - colidom

37

一种快速而紧凑的方法可能是:

def picklines(thefile, whatlines):
  return [x for i, x in enumerate(thefile) if i in whatlines]

这个函数接受任何类似于打开的文件的对象 thefile(由调用者决定它是否应该从磁盘文件、套接字或其他类似于文件的流中打开),以及一组从零开始的行索引 whatlines,并返回一个列表,具有较低的内存占用和合理的速度。如果要返回的行数很大,您可能更喜欢使用生成器:

def yieldlines(thefile, whatlines):
  return (x for i, x in enumerate(thefile) if i in whatlines)

这基本上只适用于循环。请注意,唯一的区别在于return语句中使用了圆括号而不是方括号,分别生成列表推导和生成器表达式。

进一步注意,尽管提到了“行”和“文件”,但这些函数更加通用,它们可以作用于任何可迭代对象,无论是打开的文件还是其他任何东西,根据它们的递增项数返回一个列表(或生成器)中的项目。因此,建议使用更为通用的名称;-)。


@ephemient,我不同意 - 生成器表达式读起来流畅而完美。 - Alex Martelli
优秀而优雅的解决方案,谢谢!确实,即使是大文件也应该支持生成器表达式。这还能更优雅吗? :) - Samuel Lampa
不错的解决方案,这与@AdamMatan提出的方案相比如何?Adam的解决方案可能会更快,因为它利用了额外的信息(行号单调递增),这可能会导致早期停止。我有一个无法加载到内存的10GB文件。 - Mannaggia
2
@Mannaggia 这个答案没有强调足够,但是 whatlines 应该是一个 set,因为使用集合比(排序后的)列表更快地执行 if i in whatlines。我一开始没有注意到这一点,而是设计了自己丑陋的解决方案,使用了排序后的列表(在那里我不必每次扫描列表,而 if i in whatlines 刚好做到了这一点)��但性能差异微不足道(对于我的数据),而这个解决方案更加优雅。 - Victor K

18

为了完整起见,这里是另一种选择。

让我们从Python文档中的定义开始:

切片通常包含序列的一部分的对象。使用下标符号[]创建切片时,如果给出几个数字,则在数字之间使用冒号,例如variable_name[1:3:5]。方括号(下标)符号在内部使用切片对象(或在旧版本中使用__getslice __()和__setslice__())。

虽然切片表示法通常不直接适用于迭代器,但itertools包含一个替换函数:

from itertools import islice

# print the 100th line
with open('the_file') as lines:
    for line in islice(lines, 99, 100):
        print line

# print each third line until 100
with open('the_file') as lines:
    for line in islice(lines, 0, 100, 3):
        print line

这个函数的另一个优点是它不会一直读取迭代器,直到结尾。因此你可以做更复杂的事情:

with open('the_file') as lines:
    # print the first 100 lines
    for line in islice(lines, 100):
        print line

    # then skip the next 5
    for line in islice(lines, 5):
        pass

    # print the rest
    for line in lines:
        print line

回答最初的问题:

# how to read lines #26 and #30
In [365]: list(islice(xrange(1,100), 25, 30, 4))
Out[365]: [26, 30]

2
到目前为止,在处理大文件时最好的方法。我的程序从占用8GB+的内存,降低到几乎不占用内存。虽然CPU使用率从约15%上升到约40%,但是文件的实际处理速度快了70%。我愿意在这方面做出这种权衡。谢谢! - GollyJer
2
这对我来说似乎是最符合Python风格的。谢谢! - ipetrik
目前最Pythonic! - Joey Gao

15
line = open("file.txt", "r").readlines()[7]

15
好的。但是如果用这种方式打开文件,如何执行“close()”操作呢? - Milo Wielondek
1
@0sh 我们需要关闭吗? - Ooker
4
是的,我们需要在这之后关闭。当我们使用“with”打开文件时...它会自动关闭。 - reetesh11
1
使用with open("file.txt", "r") as file:打开文件,然后通过line = file.readlines()[7]读取第8行内容。但请注意,这将整个文件读入内存。 - questionto42

14

读取文件非常快。读取一个100MB的文件只需不到0.1秒钟(请参阅我的文章使用Python读写文件)。因此,您应该完全读取它,然后逐行处理。

这里大多数答案所做的并没有错,但是风格不好。打开文件时应始终使用with,因为它确保文件会被再次关闭。

因此,您应该像这样做:

with open("path/to/file.txt") as f:
    lines = f.readlines()
print(lines[26])  # or whatever you want to do with this line
print(lines[30])  # or whatever you want to do with this line

大文件

如果您有一个非常大的文件,内存消耗是一个问题,那么您可以逐行处理它:

with open("path/to/file.txt") as f:
    for i, line in enumerate(f):
        pass  # process line i

1
在我看来,读取一个未知长度的完整文件只为获取前30行是一种非常糟糕的风格。这会导致内存消耗过大,而且对于无限流又该怎么办呢? - return42
@return42 这非常取决于应用程序。对于很多应用程序来说,假定文本文件的大小远小于可用内存是完全可以的。如果你碰巧有潜在的巨大文件,我已经编辑了我的答案。 - Martin Thoma
感谢您的补充,这与alok的答案相同。很抱歉,我不认为这取决于应用程序。在我看来,最好不要读取比所需更多的行。 - return42
4
“Reading files is incredibly fast”这句话我不大同意。实际上,文件读取非常缓慢,数据密集型程序会尽可能地尽量少进行文件读取操作。在计算机术语中,0.1秒远远称不上“快”。如果只做一次也许还可以接受(某些情况下),但如果要执行1000次,那么需要100秒时间,这在大多数情况下都无法接受。 - Michael Dorst
1
@michael dorst:我完全同意。这取决于你的应用程序,但我们需要考虑他必须读取文件。问题是:仅阅读第26和30行与阅读具有500行的文件之间的速度差异是多少。我假设它不会太多,因为如果是这样,我希望已经提到了。 - Martin Thoma

10

有些很可爱,但可以更简单地完成:

start = 0 # some starting index
end = 5000 # some ending index
filename = 'test.txt' # some file we want to use

with open(filename) as fh:
    data = fin.readlines()[start:end]

print(data)

这将仅使用列表切片,它会加载整个文件,但大多数系统将适当地最小化内存使用,它比上述大多数方法更快,并且适用于我的10G+数据文件。祝好运!


7

如果你的大型文本文件file是严格结构化的(即每行具有相同的长度l),则可以为第n行使用以下方法:

with open(file) as f:
    f.seek(n*l)
    line = f.readline() 
    last_pos = f.tell()

免责声明:此方法仅适用于长度相同的文件!


5
您可以使用seek()函数将读取头定位到文件中指定的字节位置。但是,如果您不知道在所需读取的行之前文件中已写入多少字节(字符),这种方法就无法帮助您。也许您的文件格式严格(每行都是X个字节?)或者您可以自己计算字符数(记得包括像换行符这样的不可见字符),以提高速度。

否则,您需要按照已经在此处提出的解决方案之一,逐行读取所需行之前的所有行。


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