最後からxバイトまでファイルオブジェクトからブロックを読み取ります
64KBのチャンクをループで読み取り、処理する必要がありますが、ファイルの終わりから16バイトを引いたところで停止します。最後の16バイトはtagメタデータです。
ファイルが非常に大きい可能性があるため、RAMですべてを読み取ることはできません。
私が見つけたすべての解決策は、少し不器用で、そして/または非Python的です。
with open('myfile', 'rb') as f:
while True:
block = f.read(65536)
if not block:
break
process_block(block)
もしそうなら16 <= len(block) < 65536、それは簡単です:それはこれまでの最後のブロックです。だからuseful_data = block[:-16]そしてtag = block[-16:]
場合len(block) == 65536、それは可能性が3つのことを意味:フルブロックが有用なデータであること。それとも、この64キロバイトのブロックが実際にあることを最後のブロック、そうuseful_data = block[:-16]とtag = block[-16:]。または、この64KBブロックの後に、わずか数バイト(たとえば、3バイト)の別のブロックが続くということです。したがって、この場合:useful_data = block[:-13]およびtag = block[-13:] + last_block[:3]。
これらすべてのケースを区別するよりも良い方法でこの問題に対処するにはどうすればよいですか?
注意:
このソリューションは、で開かれたファイル
open(...)だけでなく、io.BytesIO()オブジェクトでも、または離れたSFTPで開かれたファイル(でpysftp)でも機能するはずです。ファイルオブジェクトのサイズを取得することを考えていました。
f.seek(0,2) length = f.tell() f.seek(0)その後、それぞれの後に
block = f.read(65536)で終わりから遠く離れているかどうかはわかります
length - f.tell()が、完全なソリューションはあまりエレガントに見えません。
回答
すべての反復で読み取ることができます min(65536, L-f.tell()-16)
このようなもの:
from pathlib import Path
L = Path('myfile').stat().st_size
with open('myfile', 'rb') as f:
while True:
to_read_length = min(65536, L-f.tell()-16)
block = f.read(to_read_length)
process_block(block)
if f.tell() == L-16
break
これを実行しませんでしたが、その要点を理解してください。
次のメソッドはf.read()、ストリームの終了時に空のバイトオブジェクトを返すという事実のみに依存しています(EOS)。したがってf.read()、s.recv()。に置き換えるだけでソケットに採用できます。
def read_all_but_last16(f):
rand = random.Random() # just for testing
buf = b''
while True:
bytes_read = f.read(rand.randint(1, 40)) # just for testing
# bytes_read = f.read(65536)
buf += bytes_read
if not bytes_read:
break
process_block(buf[:-16])
buf = buf[-16:]
verify(buf[-16:])
それは、常にの終わりに16のバイトを残すことによって動作しbuf、最終的に少なくとも17のバイトに存在しない場合は、その最後の16ノートを処理して、EOSまでbuf、その後buf[:-16]、空のバイトオブジェクトを返します。