代码之家  ›  专栏  ›  技术社区  ›  Ben Blank Jarret Hardie

在python中如何循环到eof?

  •  9
  • Ben Blank Jarret Hardie  · 技术社区  · 16 年前

    我需要循环直到碰到一个类似文件的对象的末尾,但是我没有找到一个“明显的方法”,这让我怀疑我忽略了一些明显的东西。-)

    我有一个流(在本例中,它是一个Stringio对象,但我对一般情况也很好奇),它以“<length><data>”格式存储未知数量的记录,例如:

    data = StringIO("\x07\x00\x00\x00foobar\x00\x04\x00\x00\x00baz\x00")
    

    现在,我能想象的唯一清晰的阅读方法是使用(我认为是)一个初始化的循环,它看起来有点非pythonic:

    len_name = data.read(4)
    
    while len_name != "":
        len_name = struct.unpack("<I", len_name)[0]
        names.append(data.read(len_name))
    
        len_name = data.read(4)
    

    在C语言中,我只需要 read(4) 在 while 的测试子句,但这当然不适用于Python。有没有想过更好的方法来实现这一点?

    6 回复  |  直到 11 年前
        1
  •  25
  •   Roger Pate    16 年前

    您可以通过 iter() 带着哨兵:

    for block in iter(lambda: file_obj.read(4), ""):
      use(block)
    
        2
  •  10
  •   Roger Pate    16 年前

    你见过如何迭代文本文件中的行吗?

    for line in file_obj:
      use(line)
    

    你可以用你自己的发电机做同样的事情:

    def read_blocks(file_obj, size):
      while True:
        data = file_obj.read(size)
        if not data:
          break
        yield data
    
    for block in read_blocks(file_obj, 4):
      use(block)
    

    参见:

        3
  •  5
  •   Andrew Dalke    16 年前

    我更喜欢前面提到的基于迭代器的解决方案将其转换为for循环。另一个直接编写的解决方案是Knuth的“循环半”。

    while 1:
        len_name = data.read(4)
        if not len_name:
            break
        names.append(data.read(len_name))
    

    通过比较,您可以看到它是如何轻松地吊入自己的发电机并用作for循环的。

        4
  •  3
  •   Alex Martelli    16 年前

    我看到,正如所预测的,典型和最流行的答案是使用非常专门的生成器“一次读取4个字节”。有时,一般性并不难(而且回报更大),因此,我建议采用以下非常一般的解决方案:

    import operator
    def funlooper(afun, *a, **k):
      wearedone = k.pop('wearedone', operator.not_)
      while True:
        data = afun(*a, **k)
        if wearedone(data): break
        yield data
    

    现在您想要的循环头只是: for len_name in funlooper(data.read, 4): .

    编辑 :通过 wearedone 成语,因为一条评论指责我的前一个稍微不太一般的版本(将退出测试硬编码为 if not data: )对所有事物都有“隐藏的依赖”!-)

    常见的瑞士军刀, itertools 当然,和往常一样,也很好:

    import itertools as it
    
    for len_name in it.takewhile(bool, it.imap(data.read, it.repeat(4))): ...
    

    或者,相当于:

    import itertools as it
    
    def loop(pred, fun, *args):
      return it.takewhile(pred, it.starmap(fun, it.repeat(args)))
    
    for len_name in loop(bool, data.read, 4): ...
    
        5
  •  1
  •   Tendayi Mawushe    16 年前

    python中的eof标记是一个空字符串,因此,如果不编写用迭代器将其包装起来的函数,您所拥有的就非常接近所能得到的最佳值。我可以通过改变 while 像:

    while len_name:
        len_name = struct.unpack("<I", len_name)[0]
        names.append(data.read(len_name))
        len_name = data.read(4)
    
        6
  •  0
  •   John Keyes    16 年前

    我将使用Tendayi的建议Re函数和迭代器来提高可读性:

    def read4():
        len_name = data.read(4)
        if len_name:
            len_name = struct.unpack("<I", len_name)[0]
            return data.read(len_name)
        else:
            raise StopIteration
    
    for d in iter(read4, ''):
        names.append(d)