Warm tip: This article is reproduced from serverfault.com, please click

其他-在python原始文件IO中实现一致的块大小

(其他 - Achieving consistent block sizing in python raw file IO)

发布于 2020-12-02 21:23:27

预先提出问题:

标准库中是否有Python方式可使用for ... in ...语法(即__iter__/ __next__来解析原始二进制文件从而产生尊重buffersize参数的无需子类IOBase或其子类?

详细说明

我想使用该for ... in ...语法打开一个原始文件进行解析,并且希望该语法生成​​可预测形状的对象。对于正在解决的问题,这没有按预期发生,所以我尝试了以下测试(import numpy as np必需):

In [271]: with open('tinytest.dat', 'wb') as f:
     ...:     f.write(np.random.randint(0, 256, 16384, dtype=np.uint8).tobytes())
     ...:

In [272]: np.array([len(b) for b in open('tinytest.dat', 'rb', 16)])
Out[272]:
array([  13,  138,  196,  263,  719,   98,  476,    3,  266,   63,   51,
    241,  472,   75,  120,  137,   14,  342,  148,  399,  366,  360,
     41,    9,  141,  282,    7,  159,  341,  355,  470,  427,  214,
     42, 1095,   84,  284,  366,  117,  187,  188,   54,  611,  246,
    743,  194,   11,   38,  196, 1368,    4,   21,  442,  169,   22,
    207,  226,  227,  193,  677,  174,  110,  273,   52,  357])

我不明白为什么会出现这种随机行为,以及为什么它不尊重buffersize论点。使用read1给出了预期的字节数:

In [273]: with open('tinytest.dat', 'rb', 16) as f:
     ...:     b = f.read1()
     ...:     print(len(b))
     ...:     print(b)
     ...:
16
b'M\xfb\xea\xc0X\xd4U%3\xad\xc9u\n\x0f8}'

就是这样:在第一个程序段的末尾有一个换行符。

In [274]: with open('tinytest.dat', 'rb', 2048) as f:
     ...:     print(f.readline())
     ...:
b'M\xfb\xea\xc0X\xd4U%3\xad\xc9u\n'

果然,readline被调用来生成文件的每个块,并且它在换行值(对应于10)上跳闸。我通过IOBase定义中的代码行验证了这一阅读

571    def __next__(self):
572    line = self.readline()
573    if not line:
574        raise StopIteration
575    return line

所以我的问题是:是否还有其他buffersizepythonic方式来实现-尊重允许for ... in ...语法的原始文件行为无需子类IOBase或其子类(因此,不属于标准库)?如果不是,这种意外行为是否需要进行PEP?(或者是否值得学习期待这种行为?:)

Questioner
K. Nielson
Viewed
11
juanpa.arrivillaga 2020-12-03 06:05:18

这种行为并不意外,据记录,所有从对象派生的对象都是IOBase通过line进行迭代的在二进制模式和文本模式之间唯一改变的是行终止符的定义方式,它总是像b"\n"在二进制模式下那样定义

文档

IOBase(及其子类)支持迭代器协议,这意味着IOBase对象可以在产生流中的行时进行迭代。根据流是二进制流(生成字节)还是文本流(生成字符串),行的定义稍有不同。readline()下文。

问题在于,过去在类型系统中文本和二进制数据之间一直存在歧义,这是Python 2-> 3过渡打破向后兼容性的主要推动因素。

我认为让迭代器协议尊重在Python 3中以二进制模式打开的文件对象的缓冲区大小当然是合理的。为什么只能保留旧的行为是我只能推测的事情。

无论如何,你都应该定义自己的迭代器,这在Python中很常见。迭代器是一个基本的构建块,就像内置类型一样。

你实际上可以使用2参数iter(callable, sentinel)形式构造一个超级基本包装器:

>>> from functools import partial
>>> def iter_blocks(f, n):
...     return iter(partial(f.read, n), b'')
...
>>> np.array([len(b) for b in iter_blocks(open('tinytest.dat', 'rb'), 16)])
array([16, 16, 16, ..., 16, 16, 16])

当然,你可能只使用过一个生成器:

def iter_blocks(bin_file, n):
    result = bin_file.read(n)
    while result:
        yield result
        result = bin_file.read(n)

有很多方法可以解决这个问题。同样,迭代器是编写惯用Python的核心类型

Python是一种非常动态的语言,“鸭子打字”是游戏的名称。通常,你的第一个直觉不应该是“如何对某些内置类型进行子类化以扩展功能”。我的意思是,通常这是可能的,但是你会发现有很多语言功能旨在不必这样做,而且通常至少以我的眼光,这种表达方式更好。 。