バイナリファイルからのデータの読み込み、structモジュールを利用したバイナリファイルへのデータの書き込みと読み込みの方法を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
前回はテキストファイルを例にファイル操作の基礎を取り上げた。今回はPythonでバイナリファイルを扱う基本について見ていこう。
前回に取り上げた「テキストファイル」とは「文字や数字、記号など、人が読めるものだけで構成されたデータを含んだファイル」といえる(一部、改行コードやタブ文字など、「読める」かどうかは微妙なものもあるが)。第5回「文字列の基本」で述べたように、コンピュータで文字を扱うには、それらに番号を割り振っている。テキストファイルとは、これらの「人が読める文字に割り振られた番号」だけを含んだファイルのことともいえる*1。
*1 実際には、コンピュータが扱える文字の種類を定めた文字セット(文字集合)や、文字セットに含まれる文字をコンピュータ内部で扱えるようにどのようにして番号を割り振るかを定めた符号化方式などに応じて、テキストファイルに文字データを格納する具体的な方式には違いがある(これが「シフトJISのテキストファイル」「UTF-8のテキストファイル」など、テキストファイルにも種類がある理由だ)。
これに対して、「バイナリファイル」とは「テキストファイル以外のファイル」のことだ。音楽データやビデオデータ、プログラムの実行ファイル、プログラムが独自のフォーマットで保存するデータファイルなど、バイナリファイルに保存されるデータは数多い。
とはいえ、今ではさまざまなフォーマットのバイナリファイルをプログラマーが直接扱うことはまずないだろう。そうするためのライブラリが既に多数あるからだ。例えば、本稿では後でGIFファイルを直接オープンして、その画像の横幅や縦幅を調べてみるが、こうした作業を行うためのライブラリとしてPillowがある。ZIPファイルの圧縮/展開を行うなら標準ライブラリのzipfileを使える。このように各種バイナリファイルを扱うための便利なライブラリは多数存在しているが、以下ではバイナリファイルを直接触りながら、その扱い方の基本について見ていこう。
Pythonのプログラムから見たとき、バイナリファイルには「bytes型」で表現されるデータが格納されていると考えられる。テキストファイルとのやりとりでは、文字列(str型)のオブジェクトを渡したり、それが返されたりしたが、バイナリファイルを操作するときにはbytesオブジェクトがプログラムとファイルの間でやりとりされる。
そこでまずbytes型(とその可変バージョンであるbytearray型)について簡単にまとめておこう。
bytes型とは、0〜255の範囲の値が連続するデータのことだ。
bytes型のリテラル値は文字列と似た形で表現される。ただし、シングルクオートやダブルクオート、トリプルクオートの前に「bytes型」であることを意味する「b」が前置される点が異なる。以下はその例だ(コメントには実際の値を付記してある)。
value1 = b'a' # 97(ord('a') == 97)
value2 = b'abc' # 97, 98, 99
value3 = b'\x61' # 97(0x61 == 97)
print(value1, value2, value3)
最初の例である「b'a'」は、文字「a」のASCII値である「97」を単一の要素とするbytesオブジェクト(リテラル)である。このように、ASCIIの範囲内にあるアルファベットや数値、記号類を使って、bytes型のリテラルを記述できる。その次の「b'abc'」は、97、98、99という3つの値を要素とする(文字「a」のASCII値が「97」であることは既に説明したので、文字「b」「c」のASCII値も分かるだろう)。最後の例は、文字を使わずに文字列と同様なエスケープシーケンスを使って、「b'a'」と同じ値を持つリテラルを記述したところだ。「\x」というのは次に続く2文字を16進表記の整数値として解釈することを意味する。上の例では「\x61」となっているが、16進数の「61」は「16×6+1」を意味し、結果として10進数表記すれば「97」となる。
実行結果を以下に示す。
上に示した通り、bytes型のリテラル値は人が文字として読める範囲の値については、アルファベットなどを利用して表記されるようになっている。
この他にもbytes関数を使ってもbytesオブジェクトを作成できる。以下に例を示す。
value5 = bytes(10) # 10バイトのbytesオブジェクトを作成(全要素の値がゼロ)
value6 = bytes([97, 98, 99]) # b'abc'を作成
value7 = bytes('abc', 'utf-8') # 文字列'abc'をUTF-8でエンコードしてbytes型に
print(value5, value6, value7)
bytes関数に整数値を1つ渡すと、全ての要素がゼロ(\x00)とし、渡された整数値をそのサイズ(要素数)とするbytesオブジェクトが作成される。上の例なら、10バイトでその要素が全てゼロのbytesオブジェクトが作られる。
また、0〜255の範囲の整数値を要素とする反復可能オブジェクト(リストなど)を渡すと、それらを要素とするbytesオブジェクトが作成される。上の例では、97、98、99を要素とするリストを渡しているが、これは「b'abc'」と同じオブジェクトになる。
最後の例は、文字列とそのエンコード方法をbytes関数に渡している。これは渡した文字列を、指定したエンコード方法でエンコードした結果得られる、値の列をbytesオブジェクトの要素とする。Python 3のデフォルトのエンコード方式はUTF-8なので、これは「b'abc'」を作成する。
実行結果を以下に示す。動作を確認してほしい。
文字列と同様、bytesオブジェクトは「変更不可能」である(これに対して、bytearray型は「変更可能」。ただし、本稿ではbytearray型の説明は省略する)。その表記方法も文字列と似ているのは既に見た通りだ。ただし、文字列の要素は文字(デフォルトではUTF-8でエンコードされたUnicode文字)であり、1文字が何バイトになるかはエンコード方式次第となっているのに対して、bytes型の要素は常に1バイト(0〜255)となる。
例えば、文字列「'あ'」は1文字だが、UTF-8でエンコードされているとすると、これは実際には「\xe3」「\x81」「\x82」という3バイトの数値で表現される。そして、bytesオブジェクトに「あ」という文字を保存しようとすると、今述べた3つの値が順に保存される。実際に確認してみよう。
文字列をエンコードして、バイト列(bytesオブジェクト)を得るにはencodeメソッドを使用する。引数を指定しなければ、UTF-8形式でエンコードすることを意味する。これを使って、以下のように書けば、文字列「'あ'」に対応するbytesオブジェクトが得られる。
encoded_value = 'あ'.encode()
print(encoded_value)
実行結果を以下に示す。
上述した通り、3つの要素を持つbytesオブジェクトが返されたことが分かる。
一方、bytesオブジェクトを文字列に変換するには、decodeメソッドを使用する。こちらも引数を省略すると、UTF-8形式でエンコードされたものとして、bytesオブジェクトを処理する。
decoded_value = encoded_value.decode()
print(decoded_value)
実行結果を以下に示す。
文字列をプログラムの外部(インターネットなど)とやりとりする際には、今見たencodeメソッドとdecodeメソッドを使って、文字列とバイト列(bytesオブジェクト)に変換することがよくある。
bytesオブジェクトはシーケンスであり、シーケンスが一般にサポートする操作も実行できる(インデックスやスライスによる要素の取り出しなど)。これらについては、Pythonのドキュメント「bytes と bytearray の操作」を参照されたい。
また、変更可能なバイト列を扱う「bytearray型」もあるが、本稿では説明を省略する。
ここまでバイナリファイルを操作する際に使用するbytesオブジェクトについて簡単に見てきた。次に、実際にバイナリファイルを読み書きしてみよう。
バイナリファイルを開くには、テキストファイルと同様、open関数を使用する。以下にその基本的な構文を示す。
open(file, mode='r')
fileにオープンするファイルの名前を、modeにオープンするモードを指定する。modeに指定可能な値は以下の通り。
パラメーターmodeに指定可能な値 | 説明 |
---|---|
'r' | 読み込み用にオープン(デフォルト値) |
'w' | 書き込み用にオープン |
'a' | 追記用にオープン |
'x' | 排他的書き込み用にオープン(既にファイルがあるときにはエラーとなる) |
'b' | バイナリモード(詳細は次回に取り上げる) |
't' | テキストモード(デフォルト値) |
'+' | 更新用にオープン。'r'、'w'、'a'と一緒に指定する必要がある |
open関数のパラメーターmode |
バイナリファイルを開くときには、パラメーターmodeに「'b'」を付加する。読み込むのであれば「open(ファイル名, 'rb')」と、書き込むのであれば「open(ファイル名, 'wb')」などとなる。他の値(更新するなら'x'など)については前回の記事を参照されたい。
ここではまず、テキストファイルに文字列「'あ'」を書き込んで、それを今度はバイナリファイルとして開いてみよう。
myfile = open('myfile.txt', 'w') # テキストファイルを書き込み用にオープン
myfile.write('あ') # 文字列「'あ'」を書き込み
myfile.close()
myfile = open('myfile.txt', 'rb') # テキストファイルをバイナリモードで読み込み用にオープン
content = myfile.read()
print(content)
myfile.close()
最初の3行では、「myfile.txt」ファイルを書き込み用にオープンして、それに文字列「'あ'」を書き出して、ファイルをクローズしている。次の4行では、そのファイルを今度は「バイナリファイル」として読み込み用にオープンしている。ファイルからその内容を一括して読み込むのには、テキストファイルと同様にreadメソッドが使える。これにより、文字列「'あ'」が変数contentに読み込まれるので、今度はそれを画面に表示して、ファイルをクローズしている。
実行結果はどうなるだろう。
既に述べたが、文字列「'あ'」をUTF-8でエンコードすると、「\xe3」「\x81」「\x82」という3つのバイトで表現される。テキストファイルとして文字列「'あ'」を書き込むとは、実際にはこの3バイトを書き込むことに他ならない。よって、そのファイルをバイナリファイルとして開いて、中身を読み込めば、その3バイトが今度はbytesオブジェクトとして得られるということだ。変数contentの内容を文字列にデコードすれば、それを文字列として利用できるようになる。
print(content.decode())
実行結果を以下に示す。
もちろん、テキストファイルをテキストファイルとしてオープンして、その内容を読み出せば、それは文字列として扱える(実行結果は省略)。
myfile = open('myfile.txt')
content = myfile.read()
print(content)
myfile.close()
今見たような文字列とbytesオブジェクトとの間の変換処理はWebサーバから得られるHTMLファイルでも同様だ。静的なHTMLファイルでも動的に得られるWebページでも、WebサーバからHTMLをPythonのプログラムで取得すると、通常、それらはbytesオブジェクトになる。そのため、受け取ったHTMLを文字列として扱い、何らかの処理をそれに加えるには、それをデコードして文字列に変換する必要がある。
Copyright© Digital Advantage Corp. All Rights Reserved.