有没有一种简单的方法可以告诉我们文件指针所在的行号?

13
在Python 2.5中,我正在使用文件指针读取一个结构化文本数据文件(大小约30MB):
fp = open('myfile.txt', 'r')
line = fp.readline()
# ... many other fp.readline() processing steps, which
# are used in different contexts to read the structures

但是,在解析文件时,我遇到了一些有趣的东西,我想报告行号,以便我可以在文本编辑器中调查该文件。 我可以使用fp.tell()告诉我字节偏移量在哪里(例如16548974L),但没有"fp.tell_line_number()"来帮助我将其转换为行号。
是否有Python内置或扩展程序可以轻松跟踪和“告诉”文本文件指针所在的行号?
注意:我不要求使用line_number += 1样式计数器,因为我在不同的上下文中调用fp.readline(),这种方法需要更多的调试工作,而且并不值得在代码的正确位置插入计数器。
9个回答

18

解决这个问题的典型方法是定义一个新类,用于包装现有的file实例,并自动计数。像这样(仅仅是我脑海中的想法,未经测试):

class FileLineWrapper(object):
    def __init__(self, f):
        self.f = f
        self.line = 0
    def close(self):
        return self.f.close()
    def readline(self):
        self.line += 1
        return self.f.readline()
    # to allow using in 'with' statements 
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

使用方式如下:

f = FileLineWrapper(open("myfile.txt", "r"))
f.readline()
print(f.line)

看起来标准模块fileinput做了大致相同的事情(以及其他一些事情); 如果您喜欢,可以使用它。


+1,这是一个好的简单解决方案,因为它只需要更改open调用。您可能还想为使用的任何其他函数(例如close)提供包装器,但它们应该是相当次要的传递函数。 - paxdiablo
3
fileinput内置模块似乎可以无缝工作:fp = fileinput.input("myfile.txt"); fp.readline(); fp.lineno() - Mike T
1
同时 __iter__ = lambda self: iter(self.f) - saeedgnu
1
这样我们就可以写成:for line in f: - saeedgnu

14
您可能会发现fileinput模块很有用。它提供了一个通用接口,用于迭代任意数量的文件。以下是文档中的一些相关亮点:

fileinput.lineno()

返回刚刚读取的行的累积行号。在读取第一行之前,返回0。在读取最后一个文件的最后一行之后,返回该行的行号。

fileinput.filelineno()

返回当前文件中的行号。在读取第一行之前,返回0。在读取最后一个文件的最后一行之后,返回该行在文件内的行号。


小细节:在Python2.7中,fileinput似乎不支持with语句... - Julien

12
以下代码将在遍历文件'testfile'时打印行号(指针当前所在的行)。
file=open("testfile", "r")
for line_no, line in enumerate(file):
    print line_no     # The content of the line is in variable 'line'
file.close()

输出:

1
2
3
...

感谢您的建议。我会注意的。 - zaman sakib

1

我认为不行,不是以你期望的方式(标准内置 Python 文件句柄特性返回的方式 open)。

如果你不想手动跟踪行号或使用包装类(顺便说一下,这是GregH和senderle提出的绝妙建议),那么我认为你只能简单地使用 fp.tell() 数据并返回到文件开头,读取直至到达该位置。

这个选项还算不错,因为我假设错误条件不太可能发生。如果一切正常运作,就没有影响。

如果有错误发生,那么你需要额外的工作来重新扫描文件。如果文件很 ,那么可能会影响你的感知性能 - 如果这是个问题,你应该考虑这一点。


0
一种方法可能是迭代该行并明确计算已经查看的行数:
>>> f=open('text.txt','r')
>>> from itertools import izip
>>> from itertools import count
>>> f=open('test.java','r')
>>> for line_no,line in izip(count(),f):
...     print line_no,line

0
以下代码创建了一个名为Which_Line_for_Position(pos)的函数,该函数给出了位置pos所在的行号,即该文件中位于位置pos处的字符所在的行号

该函数可以用于任何位置作为参数,独立于文件指针当前位置的值和调用函数之前指针移动的历史记录。

因此,有了这个函数,不仅仅限于在对行进行不间断迭代时确定当前行的编号,这是Greg Hewgill解决方案的情况。

with open(filepath,'rb') as f:
    GIVE_NO_FOR_END = {}
    end = 0
    for i,line in enumerate(f):
        end += len(line)
        GIVE_NO_FOR_END[end] = i
    if line[-1]=='\n':
        GIVE_NO_FOR_END[end+1] = i+1
    end_positions = GIVE_NO_FOR_END.keys()
    end_positions.sort()

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

.

可以使用模块fileinput编写相同的解决方案:

import fileinput

GIVE_NO_FOR_END = {}
end = 0
for line in fileinput.input(filepath,'rb'):
    end += len(line)
    GIVE_NO_FOR_END[end] = fileinput.filelineno()
if line[-1]=='\n':
    GIVE_NO_FOR_END[end+1] = fileinput.filelineno()+1
fileinput.close()

end_positions = GIVE_NO_FOR_END.keys()
end_positions.sort()

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

但是这种解决方案有一些不便之处:

  • 需要导入模块fileinput
  • 它会删除文件的所有内容!!我的代码可能有问题,但我不太了解fileinput,无法找到问题。或者说,fileinput.input()函数的正常行为就是这样吗?
  • 似乎在启动任何迭代之前,文件首先被完全读取。如果是这样,对于非常大的文件,文件大小可能超过RAM的容量。我不确定这一点:我尝试使用1.5 GB的文件进行测试,但这需要很长时间,所以我暂时放弃了这一点。如果这一点正确,那么使用具有enumerate()的其他解决方案将成为一个论据。

.

例子:

text = '''Harold Acton (1904–1994)
Gilbert Adair (born 1944)
Helen Adam (1909–1993)
Arthur Henry Adams (1872–1936)
Robert Adamson (1852–1902)
Fleur Adcock (born 1934)
Joseph Addison (1672–1719)
Mark Akenside (1721–1770)
James Alexander Allan (1889–1956)
Leslie Holdsworthy Allen (1879–1964)
William Allingham (1824/28-1889)
Kingsley Amis (1922–1995)
Ethel Anderson (1883–1958)
Bruce Andrews (born 1948)
Maya Angelou (born 1928)
Rae Armantrout (born 1947)
Simon Armitage (born 1963)
Matthew Arnold (1822–1888)
John Ashbery (born 1927)
Thomas Ashe (1836–1889)
Thea Astley (1925–2004)
Edwin Atherstone (1788–1872)'''


#with open('alao.txt','rb') as f:

f = text.splitlines(True)
# argument True in splitlines() makes the newlines kept

GIVE_NO_FOR_END = {}
end = 0
for i,line in enumerate(f):
    end += len(line)
    GIVE_NO_FOR_END[end] = i
if line[-1]=='\n':
    GIVE_NO_FOR_END[end+1] = i+1
end_positions = GIVE_NO_FOR_END.keys()
end_positions.sort()


print '\n'.join('line %-3s  ending at position %s' % (str(GIVE_NO_FOR_END[end]),str(end))
                for end in end_positions)

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

print
for x in (2,450,320,104,105,599,600):
    print 'pos=%-6s   line %s' % (x,Which_Line_for_Position(x))

结果

line 0    ending at position 25
line 1    ending at position 51
line 2    ending at position 74
line 3    ending at position 105
line 4    ending at position 132
line 5    ending at position 157
line 6    ending at position 184
line 7    ending at position 210
line 8    ending at position 244
line 9    ending at position 281
line 10   ending at position 314
line 11   ending at position 340
line 12   ending at position 367
line 13   ending at position 393
line 14   ending at position 418
line 15   ending at position 445
line 16   ending at position 472
line 17   ending at position 499
line 18   ending at position 524
line 19   ending at position 548
line 20   ending at position 572
line 21   ending at position 600

pos=2        line 0
pos=450      line 16
pos=320      line 11
pos=104      line 3
pos=105      line 4
pos=599      line 21
pos=600      line None

.

然后,有了函数Which_Line_for_Position(),就很容易获得当前行的编号:只需将f.tell()作为参数传递给该函数。

但是警告:当使用f.tell()并在文件中移动文件指针时,绝对必须以二进制模式打开文件:'rb''rb+''ab'或....


0
最近在研究一个类似的问题,我想到了这个基于类的解决方案。
class TextFileProcessor(object):

    def __init__(self, path_to_file):
        self.print_line_mod_number = 0
        self.__path_to_file = path_to_file
        self.__line_number = 0

    def __printLineNumberMod(self):
        if self.print_line_mod_number != 0:
            if self.__line_number % self.print_line_mod_number == 0:
                print(self.__line_number)

    def processFile(self):
        with open(self.__path_to_file, 'r', encoding='utf-8') as text_file:
            for self.__line_number, line in enumerate(text_file, start=1):
                self.__printLineNumberMod()

                # do some stuff with line here.

print_line_mod_number属性设置为您想要记录的节奏,然后调用processFile

例如...如果您希望每100行获得反馈,则应如下所示。

tfp = TextFileProcessor('C:\\myfile.txt')
tfp.print_line_mod_number = 100
tfp.processFile()

控制台输出将是

100
200
300
400
etc...

0

使用with上下文管理器打开文件,然后在for循环中枚举行。

with open('file_name.ext', 'r') as f:
    [(line_num, line) for line_num, line in enumerate(f)]

你能否详细阐述一下您的回答? - Twenty
实际上我添加了,但不确定为什么它被忽略了。我再次添加,感谢您提醒。 - Coddy

-1
关于@eyquem的解决方案,我建议使用fileinput模块和fileinput.lineno()选项,以mode='r'方式进行操作,这对我很有效。
以下是我在代码中实现这些选项的方法。
    table=fileinput.input('largefile.txt',mode="r")
    if fileinput.lineno() >= stop : # you can disregard the IF condition but I am posting to illustrate the approach from my code.
           temp_out.close()

1
这并没有提供问题的答案。如果要批评或请求作者澄清,请在他们的帖子下留言 - 您始终可以在自己的帖子上发表评论,并且一旦您拥有足够的声望,您将能够评论任何帖子。- 来自审查 - Prune
@ Prune - 感谢您的评论,我已经包含了一段代码片段来阐明我的建议。 - speedchase
请注意,“above”没有上下文可言。 回答的投票会发生变化,回答也可以以多种不同的方式排序。最好链接到您所指的答案。 - ale

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