标准库中是否有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
所以我的问题是:是否还有其他buffersize
pythonic方式来实现-尊重允许for ... in ...
语法的原始文件行为,而无需子类IOBase
或其子类(因此,不属于标准库)?如果不是,这种意外行为是否需要进行PEP?(或者是否值得学习期待这种行为?:)
这种行为并不意外,据记录,所有从对象派生的对象都是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是一种非常动态的语言,“鸭子打字”是游戏的名称。通常,你的第一个直觉不应该是“如何对某些内置类型进行子类化以扩展功能”。我的意思是,通常这是可能的,但是你会发现有很多语言功能旨在不必这样做,而且通常至少以我的眼光,这种表达方式更好。 。
“通常,您的第一个直觉不应该是“如何对某些内置类型进行子类化以扩展功能”。这非常有帮助,谢谢。