Pythonでは、バイナリファイルを読み込んでそのファイルの各バイトをループする方法を教えてください。
Python 2.4以前
f = open("myfile", "rb")
try:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
finally:
f.close()
Python 2.5-2.7
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != "":
# Do stuff with byte.
byte = f.read(1)
Withステートメントは、2.5より下のバージョンのPythonでは利用できないことに注意してください。バージョン2.5で使用するにはインポートする必要があります。
from __future__ import with_statement
2.6ではこれは不要です。
Python 3
Python 3では、少し違います。バイトモードではなく、バイトオブジェクトでストリームから生の文字を取得することはなくなります。そのため、条件を変更する必要があります。
with open("myfile", "rb") as f:
byte = f.read(1)
while byte != b"":
# Do stuff with byte.
byte = f.read(1)
あるいはbenhoytが言っているように、等しくないことをスキップし、b""
がfalseと評価されるという事実を利用します。これにより、2.6と3.xの間でコードを変更することなく互換性があります。また、バイトモードからテキストまたはその逆に移動した場合に条件を変更しなくて済みます。
with open("myfile", "rb") as f:
byte = f.read(1)
while byte:
# Do stuff with byte.
byte = f.read(1)
このジェネレータは、ファイルからチャンク単位でファイルを読み取り、ファイルからバイトを取り出します。
def bytes_from_file(filename, chunksize=8192):
with open(filename, "rb") as f:
while True:
chunk = f.read(chunksize)
if chunk:
for b in chunk:
yield b
else:
break
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
iterators と generators についての情報はPythonのドキュメントを参照してください。
ファイルがそれほど大きくない場合、それをメモリに保持することが問題になります。
bytes_read = open("filename", "rb").read()
for b in bytes_read:
process_byte(b)
process_byteは、渡されたバイトに対して実行したい操作を表します。
一度にチャンクを処理したい場合:
file = open("filename", "rb")
try:
bytes_read = file.read(CHUNKSIZE)
while bytes_read:
for b in bytes_read:
process_byte(b)
bytes_read = file.read(CHUNKSIZE)
finally:
file.close()
ファイルを読み取るには(一度に1バイト(バッファリングは無視))、 two-argument iter(callable, sentinel)
built-in function を使用できます。
with open(filename, 'rb') as file:
for byte in iter(lambda: file.read(1), b''):
# Do stuff with byte
b''
(空のバイト文字列)を何も返さないまで、file.read(1)
を呼び出します。大きなファイルの場合、メモリは無制限に増えません。 buffering=0
をopen()
に渡して、バッファリングを無効にすることができます。これにより、反復ごとに1バイトのみが読み取られることが保証されます(低速)。
with
- statementは、ファイルを自動的に閉じます—その下のコードが例外を発生させる場合を含みます。
デフォルトでは内部バッファリングが存在しますが、一度に1バイトを処理することは依然として非効率的です。たとえば、与えられたすべてを食べるblackhole.py
ユーティリティは次のとおりです。
#!/usr/bin/env python3
"""Discard all input. `cat > /dev/null` analog."""
import sys
from functools import partial
from collections import deque
chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15)
deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0)
例:
$ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py
私のマシンではchunksize == 32768
のとき〜1.5 GB/sを処理し、chunksize == 1
のときは〜7.5 MB/sのみを処理します。つまり、一度に1バイトを読み取るのは200倍遅くなります。一度に複数のバイトを使用するように処理を書き直すことができ、パフォーマンスが必要な場合はifを考慮してください。
mmap
を使用すると、ファイルを bytearray
およびファイルオブジェクトとして同時に処理できます。両方のインターフェイスにアクセスする必要がある場合は、ファイル全体をメモリにロードする代わりに使用できます。特に、プレーンなfor
- loopを使用するだけで、メモリマップファイルで一度に1バイトを繰り返すことができます。
from mmap import ACCESS_READ, mmap
with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s:
for byte in s: # length is equal to the current file size
# Do stuff with byte
mmap
はスライス表記をサポートしています。たとえば、mm[i:i+len]
は、len
の位置から始まるファイルからi
バイトを返します。コンテキストマネージャープロトコルは、Python 3.2より前ではサポートされていません。この場合、mm.close()
を明示的に呼び出す必要があります。 mmap
を使用して各バイトを反復処理すると、file.read(1)
よりも多くのメモリを消費しますが、mmap
は1桁高速です。
クリスピー、Skurmedel、Ben Hoyt、Peter Hansenの優れた点をすべてまとめると、これはバイナリーファイルを一度に1バイトずつ処理するのに最適なソリューションです。
with open("myfile", "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
do_stuff_with(ord(byte))
Pythonバージョン2.6以降では、以下の理由があります。
または、速度を向上させるためにJ. F. Sebastiansソリューションを使用してください。
from functools import partial
with open(filename, 'rb') as file:
for byte in iter(partial(file.read, 1), b''):
# Do stuff with byte
あるいは、codeapeで示されているようなジェネレータ関数として使用したい場合は、
def bytes_from_file(filename):
with open(filename, "rb") as f:
while True:
byte = f.read(1)
if not byte:
break
yield(ord(byte))
# example:
for b in bytes_from_file('filename'):
do_stuff_with(b)
Pythonでバイナリファイルを読み、各バイトをループする
Python 3.5で新たに追加されたのはpathlib
モジュールです。これは特にバイト単位でファイルを読み込む便利なメソッドを持っています。私はこれをまともな(素早いものであれば)答えだと思います。
import pathlib
for byte in pathlib.Path(path).read_bytes():
print(byte)
これがpathlib
に言及する唯一の答えであることに興味深い。
Python 2では、おそらくこれを行うでしょう(Vinay Sajipも示唆しているように)。
with open(path, 'b') as file:
for byte in file.read():
print(byte)
ファイルが大きすぎてメモリー内で反復できない場合は、iter
関数をcallable, sentinel
シグニチャー(Python 2バージョン)を使用して、慣用的にチャンク化します。
with open(path, 'b') as file:
callable = lambda: file.read(1024)
sentinel = bytes() # or b''
for chunk in iter(callable, sentinel):
for byte in chunk:
print(byte)
(他のいくつかの答えはこれに言及しますが、賢明な読み取りサイズを提供するものはほとんどありません。)
Python 3.5以降の標準ライブラリの慣用的な使い方も含めて、これを行う関数を作成しましょう。
from pathlib import Path
from functools import partial
from io import DEFAULT_BUFFER_SIZE
def file_byte_iterator(path):
"""given a path, return an iterator over the file
that lazily loads the file
"""
path = Path(path)
with path.open('rb') as file:
reader = partial(file.read1, DEFAULT_BUFFER_SIZE)
file_iterator = iter(reader, bytes())
for chunk in file_iterator:
for byte in chunk:
yield byte
file.read1
を使っていることに注意してください。 file.read
は、要求されたすべてのバイトまたはEOF
を取得するまでブロックします。 file.read1
はブロッキングを避けることを可能にします、そしてそれはこれのためにより速く戻ることができます。他の回答でもこれについて言及していません。
メガバイト(実際にはメガバイト)の疑似乱数データでファイルを作りましょう:
import random
import pathlib
path = 'pseudorandom_bytes'
pathobj = pathlib.Path(path)
pathobj.write_bytes(
bytes(random.randint(0, 255) for _ in range(2**20)))
それではそれを繰り返して、それをメモリ内で具体化しましょう。
>>> l = list(file_byte_iterator(path))
>>> len(l)
1048576
最後の100バイトと最初の100バイトなど、データの任意の部分を調べることができます。
>>> l[-100:]
[208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181]
>>> l[:100]
[28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29]
次のことをしないでください - これは改行文字に到達するまで任意のサイズの塊を引っ張ります - 塊が小さすぎると遅すぎるし、大きすぎるかもしれません:
with open(path, 'rb') as file:
for chunk in file: # text newline iteration - not for bytes
for byte in chunk:
yield byte
上記は、意味的に人間が読める形式のテキストファイル(プレーンテキスト、コード、マークアップ、マークダウンなど...基本的にはASCII、UTF、ラテンなど、エンコードされたもの)に対してのみ有効です。
Python 3では、一度にすべてのファイルを読みます。
with open("filename", "rb") as binary_file:
# Read the whole file at once
data = binary_file.read()
print(data)
あなたはdata
変数を使ってあなたが望むものなら何でも繰り返すことができます。
たくさんのバイナリデータを読むのであれば、 structモジュール を検討した方が良いでしょう。それは "CとPythonの型の間の変換"として文書化されていますが、もちろん、バイトはバイトであり、それらがCの型として作成されたかどうかは関係ありません。たとえば、バイナリデータに2つの2バイト整数と1つの4バイト整数が含まれている場合、それらを次のように読み取ることができます(struct
のドキュメントの例)。
>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
ファイルの内容を明示的にループ処理するよりも、この方法の方が便利、高速、またはその両方の場合があります。
上記すべてを試して@Aaron Hallからの回答を使用した後、Window 10、8 Gb RAM、およびPython 3.5 32ビットを実行しているコンピューターで〜90 Mbのファイルのメモリエラーが発生しました。私は代わりにnumpy
を使うことを同僚から勧められました、そしてそれは不思議に働きます。
はるかに、(私がテストした)バイナリファイル全体を読むのが最も速いのは、次のとおりです。
import numpy as np
file = "binary_file.bin"
data = np.fromfile(file, 'u1')
これまでに他のどの方法よりも多くの速度が速いです。誰かに役立つことを願っています!
あなたがスピーディなものを探しているなら、ここで私は何年も働いている私が使用してきた方法があります:
from array import array
with open( path, 'rb' ) as file:
data = array( 'B', file.read() ) # buffer the file
# evaluate it's data
for byte in data:
v = byte # int value
c = chr(byte)
intの代わりにcharsを反復したい場合は、単純にdata = file.read()
を使用できます。これはpy3のbytes()オブジェクトであるべきです。