10

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

我有一个流(在这种情况下,它是一个 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 的语言中,我只需将 's test 子句read(4)放在while's test 子句中,但这当然不适用于 Python。关于更好的方法来实现这一点的任何想法?

4

6 回答 6

27

您可以通过iter()将迭代与哨兵结合起来:

for block in iter(lambda: file_obj.read(4), ""):
  use(block)
于 2009-11-17T22:09:52.213 回答
10

你见过如何遍历文本文件中的行吗?

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)

也可以看看:

于 2009-11-17T22:00:37.657 回答
5

我更喜欢已经提到的基于迭代器的解决方案,以将其转换为 for 循环。另一个直接写的解决方案是 Knuth 的“loop-and-a-half”

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

通过比较,您可以看到它是如何轻松提升到自己的生成器中并用作 for 循环的。

于 2009-11-17T22:06:18.177 回答
3

正如预测的那样,我看到典型和最流行的答案是使用非常专业的生成器“一次读取 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): ...
于 2009-11-17T22:02:49.767 回答
1

python 中的 EOF 标记是一个空字符串,因此您所拥有的非常接近您将获得的最佳值,而无需编写函数将其包装在迭代器中。我可以通过改变while类似的方式以更 Pythonic 的方式编写:

while len_name:
    len_name = struct.unpack("<I", len_name)[0]
    names.append(data.read(len_name))
    len_name = data.read(4)
于 2009-11-17T22:05:15.637 回答
0

我会选择 Tendayi 的建议 re function 和 iterator 以提高可读性:

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)
于 2009-11-17T22:20:44.187 回答