【问题标题】:How to start iterating a file at specific line?如何在特定行开始迭代文件?
【发布时间】:2017-04-11 20:31:53
【问题描述】:

我正在使用enumerate() 遍历文件的行,有时需要从特定的文件行开始迭代,所以我尝试了testfile.seek(),例如如果我想在第 10 行再次开始迭代文件,那么 testfile.seek(10):

test_file.seek(10)

for i, line in enumerate(test_file):
    …

然而test_file 总是从第一行 0 开始迭代。 我可能做错了什么?为什么seek() 不起作用?任何更好的实现也将不胜感激。

提前谢谢你,一定会支持/接受答案

【问题讨论】:

  • seek(10) 不是转到文件的第 10 个字节吗?
  • 你读过the docs的seek方法吗?
  • 我认为提及您特别关注效率是明智的。这样你可能更有可能得到关于 linecache / islice 的答案,我认为这是最快的选择。

标签: python file for-loop iteration seek


【解决方案1】:

普通文件是字符的序列,在文件系统级别,就Python而言;没有低级的方法可以跳转到特定的行。 seek() 命令以字节而不是行为单位计算偏移量。 (原则上,仅当文件以二进制模式打开时才应使用显式查找偏移量。在文本文件上查找是"undefined behavior",因为逻辑字符可以占用多个字节。)

如果您想跳过多行,您唯一的选择是阅读并丢弃它们。由于迭代文件对象一次获取一行,因此使用itertools.islice()

from itertools import islice

skipped = islice(test_file, 10, None)  # Skip 10 lines, i.e. start at index 10
for i, line in enumerate(skipped, 11):
    print(i, line, end="")
    ...

【讨论】:

  • 我认为你是对的。为了找到第 10 行,Python 必须首先找到 9 个换行符,可以是任何地方。
  • iseek?或者你的意思是islice
  • 该死的,当然是@MosesKoledoye :-) 感谢您的关注。
  • ;-) 感谢您击败我错过了import 修复
  • 我会说行号不是 0 索引的。 OP 可能想跳过 9 行。
【解决方案2】:

执行此操作的原生 Python 方法是使用 zip 迭代不必要的行。

with open("text.txt","r") as test_file:
    for _ in zip(range(10), test_file): pass
    for i, line in enumerate(test_file,start=10):
        print(i, line)

【讨论】:

    【解决方案3】:

    我个人只会使用 if 语句。可能很简陋,但至少很容易理解。

    with open("file") as fp:
    for i, line in enumerate(fp):
        if i >= 10:
            # do stuff.
    

    编辑:islice: 在这里进行的比较:Python fastest access to line in file 比我的能力要好。结合 itertools 手册:https://docs.python.org/2/library/itertools.html 我怀疑你需要更多

    【讨论】:

    • 但更喜欢 seek() 进行优化。所以他们不需要遍历不必要的行
    • @JoKo 啊,如果考虑到效率,那么我会推荐 itertools.islice。然后你甚至不需要将使用过的行加载到内存中。
    • 你介意用itertools.islice 举个例子吗?
    • @Jo Ko:你不能。一行是由某些字符定义的,SOMETHING 必须读取它们才能知道它们在哪里,除非您为文件建立了外部索引。
    • 是的,islice 不会阻止将每一行加载到内存中,它只是懒惰地迭代行。这个解决方案也是如此,但islice 用于获取slices
    【解决方案4】:

    seek 方法可以帮助您的唯一方法是,如果文件中的所有行都具有相同的长度,您提前知道,并且您的文件是二进制或至少是纯 ascii 文本(即不允许使用可变宽度字符)。那你就真的可以了

    test_file.seek(10 * (length_of_line + 1), os.SEEK_SET)
    

    这是因为seek 会将内部文件指针移动固定的字节数,而不是行数。上面的+1 是为了说明换行符。您可能必须在 Windows 机器上使用 \r\n 行终止符使其成为 +2

    如果您的文件是非 ascii 文件,这将不起作用,因为某些行的字符长度可能相同,但实际上包含不同数量的字节,从而导致对 seek 的调用产生未定义的结果。

    有几种合法的方法可以跳过前 10 行:

    1. 将整个文件读入 list 并丢弃前 10 行:

      with open(...) as test_file:
          test_data = list(test_file)[10:]
      

      现在test_data 包含文件中除前 10 行之外的所有行。

    2. 使用enumeratefor 循环中读取文件时丢弃文件中的行:

      with open(...) as test_file:
          for lineno, line in test_file:
              if lineno < 10:
                  continue
              # Do something with the line
      

      此方法的优点是不存储不必要的行。这在功能上类似于使用 itertools.islice 作为其他一些答案的建议。

    3. 在正常继续之前,使用一些非常神秘的低级东西从文件中实际读取 10 个换行符。您可能必须预先指定文件的编码才能正确处理文本 I/O,但它应该可以开箱即用地处理 ASCII 文件(有关详细信息,请参阅here):

      newline_count = 10
      with open(..., encoding='utf-8') as test_file:
          while newline_count > 0:
              next_char = test_file.read(1)
              if next_char == '\n':
                  newline_count -= 1
          # You have skipped 10 lines, so process normally here.
      

      这个选项不是特别健壮。它不会优雅地处理少于 10 行的情况,并且非常粗略地重新实现了内置文件迭代器的内部机制。它提供的唯一可能优势是它不像迭代器那样缓冲整行。

    【讨论】:

    • 除非是二进制文件,否则test_file.seek(10 * (length_of_line + 1))是未定义的。来自 Python 文档:“偏移量必须是 TextIOBase.tell() 返回的数字或零。任何其他偏移量值都会产生未定义的行为。”
    • @iafisher。接得好。固定。
    • 我觉得还是错了。 whence 参数(第二个)默认为 os.SEEK_SET 反正;问题是offset 参数(第一个)只能是0 或调用tell 返回的值。这与in C's fseek function 的限制相同。
    • @iafisher。你说的对。我认为非 ascii 文本文件会出现问题,因为即使像 read(1) 这样的低级函数也可以将多字节字符作为一个单元返回。我将添加一个类似于我在第 3 项中所做的符号。
    • @iafisher。如果您同意最新的编辑,请告诉我。我认为它纠正了您注意到的问题。
    【解决方案5】:

    除非您知道所需行的第一个字符的字节偏移量,否则您不能使用 seek() 到达特定行的开头。

    一种简单的方法是在itertools 模块中使用islice() 迭代器。

    例如,假设您有一个非常测试的输入文件,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ...
    

    示例代码:

    from __future__ import print_function
    from itertools import islice
    
    with open('test_file.txt') as test_file:
        for i, line in enumerate(islice(test_file, 9, None), 10):
            print('line #{}: {}'.format(i, line), end='')
    

    输出:

    line #10: 10
    line #11: 11
    line #12: 12
    line #13: 13
    line #14: 14
    line #15: 15
    line #16: 16
    line #17: 17
    line #18: 18
    line #19: 19
    line #20: 20
    line #21: 21
    line #22: 22
    ...
    

    注意islice() 从零开始计数,这就是为什么它的第一个参数是9 而不是10。此外,这并不像 seek() 那样快,因为 islice() 实际上会读取所有行,直到到达您想要开始的行。

    【讨论】:

      猜你喜欢
      • 2014-01-15
      • 2020-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多