webdevqa.jp.net

バイナリファイルの読み込みと各バイトのループ

Pythonでは、バイナリファイルを読み込んでそのファイルの各バイトをループする方法を教えてください。

318
Jesse Vogt

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)
328
Skurmedel

このジェネレータは、ファイルからチャンク単位でファイルを読み取り、ファイルからバイトを取り出します。

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)

iteratorsgenerators についての情報はPythonのドキュメントを参照してください。

153
codeape

ファイルがそれほど大きくない場合、それをメモリに保持することが問題になります。

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()
48
Vinay Sajip

ファイルを読み取るには(一度に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=0open()に渡して、バッファリングを無効にすることができます。これにより、反復ごとに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桁高速です。

32
jfs

クリスピー、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以降では、以下の理由があります。

  • pythonバッファを内部的に - チャンクを読む必要はありません
  • DRYの原則 - 読み取り行を繰り返さないでください
  • withステートメントを使用すると、ファイルをクリーンに閉じることができます。
  • これ以上バイトがない場合(バイトがゼロの場合ではない)、 'byte'はfalseと評価されます。

または、速度を向上させるために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)
19
Holger Bille

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、ラテンなど、エンコードされたもの)に対してのみ有効です。

16
Aaron Hall

Python 3では、一度にすべてのファイルを読みます。

with open("filename", "rb") as binary_file:
    # Read the whole file at once
    data = binary_file.read()
    print(data)

あなたはdata変数を使ってあなたが望むものなら何でも繰り返すことができます。

5
Mircea

たくさんのバイナリデータを読むのであれば、 structモジュール を検討した方が良いでしょう。それは "CとPythonの型の間の変換"として文書化されていますが、もちろん、バイトはバイトであり、それらがCの型として作成されたかどうかは関係ありません。たとえば、バイナリデータに2つの2バイト整数と1つの4バイト整数が含まれている場合、それらを次のように読み取ることができます(structのドキュメントの例)。

>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)

ファイルの内容を明示的にループ処理するよりも、この方法の方が便利、高速、またはその両方の場合があります。

4
gerrit

上記すべてを試して@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')

参照

これまでに他のどの方法よりも多くの速度が速いです。誰かに役立つことを願っています!

3
Rick M.

あなたがスピーディなものを探しているなら、ここで私は何年も働いている私が使用してきた方法があります:

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()オブジェクトであるべきです。

2
Tcll