Pythonでバイナリデータを扱う方法

プログラミング

Pythonにおけるバイナリデータ操作

Pythonでは、バイナリデータ(バイト列)を扱うための強力な機能が提供されています。これは、ファイルI/O、ネットワーク通信、暗号化、画像処理など、様々な場面で不可欠です。

バイト列と文字列の違い

Python 3における最も重要な変更点の一つは、文字列(str)とバイト列(bytes)の明確な区別です。

  • 文字列 (str): Unicode文字のシーケンスを表します。人間が読めるテキストを扱うのに適しています。
  • バイト列 (bytes): 0から255までの整数のシーケンス(バイト)を表します。ファイルやネットワークから読み書きされる生のデータ、またはエンコードされたテキストを扱います。

これらは異なる型であり、互いに直接操作することはできません。必要に応じてエンコード/デコード処理を行う必要があります。

バイト列の生成

バイト列を生成する方法はいくつかあります。

リテラル

バイトリテラルは、文字列リテラルの前にbを付けることで作成できます。


binary_data = b'Hello, world!'
print(type(binary_data)) # 

bytes()コンストラクタ

bytes()コンストラクタは、様々な方法でバイト列を生成できます。

  • 整数シーケンスから: 0から255までの整数のリストやタプルを渡します。
  • 
    byte_list = [72, 101, 108, 108, 111] # ASCII 'Hello'
    bytes_from_list = bytes(byte_list)
    print(bytes_from_list) # b'Hello'
    
    
  • 文字列とエンコーディングから: 文字列とエンコーディングを指定してバイト列に変換します。
  • 
    text = "こんにちは"
    encoded_text = bytes(text, 'utf-8')
    print(encoded_text) # b'xe3x81x93xe3x82x93xe3x81xabxe3x81xa1xe3x81xaf'
    
    
  • サイズ指定: 指定したサイズのゼロで埋められたバイト列を生成します。
  • 
    zero_bytes = bytes(10)
    print(zero_bytes) # b'x00x00x00x00x00x00x00x00x00x00'
    
    

bytearray()コンストラクタ

bytearray()は、bytes()と似ていますが、生成されるオブジェクトはミュータブル(変更可能)です。バイト列を後から変更したい場合に便利です。


mutable_bytes = bytearray(b'abc')
mutable_bytes[0] = 120 # 'x'
print(mutable_bytes) # bytearray(b'xbc')

バイト列の操作

バイト列はシーケンス型なので、スライス、インデックスアクセス、連結などの操作が可能です。

インデックスアクセスとスライス

バイト列の要素は個々のバイト(整数)としてアクセスできます。スライスも同様にバイト列を返します。


data = b'Python'
print(data[0])       # 80 (ASCII for 'P')
print(data[1:4])     # b'yth'
print(data[-1])      # 110 (ASCII for 'n')

連結

+演算子を使用してバイト列を連結できます。


part1 = b'Hello'
part2 = b' '
part3 = b'World'
full_data = part1 + part2 + part3
print(full_data) # b'Hello World'

繰り返し

*演算子を使用してバイト列を繰り返すことができます。


repeated_data = b'-' * 5
print(repeated_data) # b'-----'

メンバーシップテスト

in演算子を使用して、バイト列に特定のバイトまたはバイト列が含まれているかを確認できます。


text_data = b'example'
print(b'a' in text_data)     # True
print(b'xyz' in text_data)   # False

エンコードとデコード

人間が読めるテキスト(文字列)とコンピュータが扱うバイナリデータ(バイト列)の間には、エンコードとデコードという変換プロセスが必要です。

エンコード

文字列をバイト列に変換するプロセスです。str.encode()メソッドを使用します。

  • エンコーディングの指定: 一般的に使用されるエンコーディングには、'utf-8''ascii''latin-1'などがあります。'utf-8'は多言語対応で広く推奨されています。
  • 
    japanese_text = "文字列"
    utf8_bytes = japanese_text.encode('utf-8')
    print(utf8_bytes)
    
    ascii_text = "Hello"
    ascii_bytes = ascii_text.encode('ascii')
    print(ascii_bytes)
    
    
  • エラーハンドリング: エンコーディングできない文字が含まれている場合のエラー処理方法を指定できます。
    • 'strict' (デフォルト): エラーが発生した場合にUnicodeEncodeErrorを送出します。
    • 'ignore': エンコードできない文字を無視します。
    • 'replace': エンコードできない文字を'?'などの置換文字に置き換えます。
    • 'xmlcharrefreplace': XML文字参照に置き換えます。
    • 'backslashreplace': バックスラッシュエスケープシーケンスに置き換えます。
    
    # 例: ASCIIでエンコードできない文字
    text_with_emoji = "😊"
    try:
        text_with_emoji.encode('ascii')
    except UnicodeEncodeError as e:
        print(f"Strict error: {e}")
    
    ignored_bytes = text_with_emoji.encode('ascii', errors='ignore')
    print(f"Ignored: {ignored_bytes}")
    
    replaced_bytes = text_with_emoji.encode('ascii', errors='replace')
    print(f"Replaced: {replaced_bytes}")
    
    

デコード

バイト列を文字列に変換するプロセスです。bytes.decode()メソッドを使用します。

  • エンコーディングの指定: エンコード時と同じエンコーディングを指定する必要があります。
  • 
    byte_data = b'xe3x81x93xe3x82x93xe3x81xabxe3x81xa1xe3x81xaf' # UTF-8 encoded "こんにちは"
    decoded_text = byte_data.decode('utf-8')
    print(decoded_text)
    
    
  • エラーハンドリング: デコードできないバイトシーケンスが含まれている場合のエラー処理方法を指定できます。'strict''ignore''replace''surrogateescape''backslashreplace'などがあります。
  • 
    # 例: 不正なUTF-8バイト
    invalid_bytes = b'xffxfe'
    try:
        invalid_bytes.decode('utf-8')
    except UnicodeDecodeError as e:
        print(f"Strict error: {e}")
    
    ignored_text = invalid_bytes.decode('utf-8', errors='ignore')
    print(f"Ignored: {ignored_text}")
    
    replaced_text = invalid_bytes.decode('utf-8', errors='replace')
    print(f"Replaced: {replaced_text}")
    
    

ファイルI/Oにおけるバイナリデータ

ファイル操作では、テキストモードとバイナリモードを区別することが重要です。

バイナリモードでのファイル読み書き

ファイルをバイナリモードで開くには、open()関数のモード引数に'b'を追加します(例: 'rb'(読み込み)、'wb'(書き込み)、'ab'(追記))。

  • 読み込み: read()メソッドはバイト列を返します。
  • 
    # ファイルにバイナリデータを書き込む例(実行前に my_binary_file.bin が存在しない場合)
    with open('my_binary_file.bin', 'wb') as f:
        f.write(b'This is binary data.')
    
    # バイナリモードでファイルを読み込む
    with open('my_binary_file.bin', 'rb') as f:
        binary_content = f.read()
        print(binary_content) # b'This is binary data.'
    
    
  • 書き込み: write()メソッドはバイト列を受け取ります。
  • 
    new_data = b'x01x02x03x04'
    with open('output.bin', 'wb') as f:
        f.write(new_data)
    
    

テキストモードでファイルを開いた場合、Pythonは自動的にエンコード/デコード処理を行いますが、バイナリモードでは生のバイト列がそのまま扱われます。画像ファイル、実行ファイル、圧縮ファイルなどは必ずバイナリモードで扱う必要があります。

structモジュール

structモジュールは、C言語の構造体のように、バイト列をPythonの数値型(整数、浮動小数点数など)とパック/アンパックするための機能を提供します。これは、特定のフォーマットのバイナリファイルを解析したり、ネットワークプロトコルに準拠したデータを生成したりする際に役立ちます。

フォーマット文字列

structモジュールは、バイト列の構造を定義するフォーマット文字列を使用します。フォーマット文字には、'b'(signed char)、'B'(unsigned char)、'h'(short)、'i'(int)、'f'(float)、'd'(double)などがあります。

パック (Pack)

Pythonの値を指定されたフォーマットのバイト列に変換します。


import struct

# 整数と浮動小数点数をパックする
# '<' はリトルエンディアン、'i' は整数、'f' は浮動小数点数
packed_data = struct.pack('<if', 10, 3.14)
print(packed_data) # 例: b'x0ax00x00x00xc3xf5H@' (エンディアンにより異なります)

アンパック (Unpack)

バイト列を指定されたフォーマットに従ってPythonの値に変換します。


import struct

packed_data = b'x0ax00x00x00xc3xf5H@' # 上記でパックしたデータ(例)
unpacked_values = struct.unpack('<if', packed_data)
print(unpacked_values) # (10, 3.140000104400263)

エンディアン(バイトの並び順)の指定も重要です。'<'はリトルエンディアン、'>'はビッグエンディアン、'='はネイティブエンディアンなどを指定できます。

memoryview

memoryviewは、バイト列やバイト配列のコピーを作成せずに、そのメモリ内容へのビューを提供します。これは、大きなバイナリデータブロックを操作する際に、メモリ効率を向上させるのに役立ちます。


data = b'This is a large byte string...'
view = memoryview(data)

# スライス操作は、元のデータのメモリを参照する
sub_view = view[5:10]
print(sub_view.tobytes()) # b'is a '

# 別のオブジェクトにコピーせずに、元のデータを変更できる(bytearrayの場合)
mutable_data = bytearray(b'Mutable example')
mutable_view = memoryview(mutable_data)
mutable_view[0] = ord('X')
print(mutable_data) # bytearray(b'Xutable example')

まとめ

Pythonにおけるバイナリデータの扱いは、bytes型、bytearray型、そしてエンコード/デコード処理が中心となります。これらの基本を理解することで、テキストデータだけでなく、様々な形式のバイナリデータを効率的かつ正確に処理できるようになります。structモジュールは構造化されたバイナリデータの操作に、memoryviewはメモリ効率の良い操作にそれぞれ強力なツールとなります。