Pythonでファイル操作やネットワーク通信などを行う際、テキストデータだけでなくバイナリデータを扱う必要があるケースは少なくありません。画像や音声、動画などのファイルを処理したり、通信プロトコルのデータを扱ったりする場合、文字列(str)ではなくバイト列としての操作が必須になります。
本記事では、特にbytesとbytearrayを中心に、Pythonでバイナリデータを扱う方法や注意点について解説します。
bytes
bytesは不変(イミュータブル)なバイト列を表す組み込み型です。文字列(str)がUnicode文字を扱うのに対し、bytesは0〜255の数値を要素とするバイト列です。
宣言方法
# リテラルでの宣言
binary_data = b"Hello"
print(binary_data) # b'Hello'
# bytesコンストラクタ
binary_data_2 = bytes([72, 101, 108, 108, 111])
print(binary_data_2) # b'Hello'
b”文字列”のように接頭辞bを付けることで、bytesオブジェクトを直接生成できます。またbytes(…)コンストラクタに数値のリスト(またはイテラブル)を渡すと、その数値をバイト値として生成します。
特徴
不変(Immutable)文字列と同様に、一度作成したbytesオブジェクトの内容は変更できません。
b_data = b"ABC"
# b_data[0] = 65 # エラー: TypeErrorが発生
スライス/インデックス参照文字列と同じようにインデックスやスライス構文を利用できますが、値の書き換えはできません。
b_data = b"Python"
print(b_data[0]) # 80 (ASCIIコードの'P'は80)
print(b_data[2:4]) # b'th'
文字列との相互変換エンコード(encode)・デコード(decode)を介して、strとbytesを相互に変換できます。
b_data = b"\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf" # こんにちは
text_data = b_data.decode("utf-8")
print(text_data) # こんにちは
new_b_data = text_data.encode("utf-8")
print(new_b_data) # b'\xe3\x81\x93\xe3\x82\x93...'
bytearray
bytearrayは可変(ミュータブル)なバイト列を表すクラスです。bytesとよく似ていますが、要素の変更や拡張ができる点が大きく異なります。
宣言方法
# bytesから初期化
mutable_binary_data = bytearray(b"Hello")
print(mutable_binary_data) # bytearray(b'Hello')
# bytearrayコンストラクタ
mutable_binary_data_2 = bytearray([72, 101, 108, 108, 111])
print(mutable_binary_data_2) # bytearray(b'Hello')
特徴
可変(Mutable)一度生成したbytearrayオブジェクトでも、インデックスで直接要素を上書きできます。
b_array = bytearray(b"ABC")
b_array[0] = 97 # 'A'(65) -> 'a'(97)
print(b_array) # bytearray(b'aBC')
スライス操作・拡張スライス構文でまとめて置き換えたり、appendやextendで要素を追加できます。
b_array = bytearray(b"ABC")
b_array[1:3] = b"Z"
print(b_array) # bytearray(b'AZ')
b_array.append(68) # 'D'(68)を追加
print(b_array) # bytearray(b'AZD')
b_array.extend(b"EF")
print(b_array) # bytearray(b'AZDEF')
byteとbytearrayの違い
bytes | bytearray | |
---|---|---|
可変性 | 不変(Immutable) | 可変(Mutable) |
適した用途 | 変更のないバイナリ | 頻繁に変更するバイナリ |
- 例えば暗号化などでバイト列を頻繁に操作する場合はbytearrayが便利です。
- 外部ライブラリやAPIでbytesが要求されている場合はbytesを使う必要があります。
bytesとbytearrayの操作
bytesやbytearrayには、文字列メソッドと似た操作が用意されており、バイト列を対象にしたメソッドが多数用意されています。以下で代表的なものを見てみましょう。
count(sub[, start[, end]])
指定した部分列(sub)が登場する回数を返します。startとendを指定すると、スライス範囲での検索が可能です。
data = b"abcabc"
print(data.count(b"abc")) # 2
print(data.count(b"abc", 1)) # 1 (先頭をスキップ)
find(sub[, start[, end]]) / index(sub[, start[, end]])
subが見つかった最初のインデックスを返します。findは見つからなければ-1を、indexはValueErrorを発生させます。
data = b"abcdef"
print(data.find(b"cd")) # 2
print(data.find(b"zz")) # -1
startswith(prefix[, start[, end]]) / endswith(suffix[, start[, end]])
指定したバイト列で始まっているか、終わっているかを確認します。
data = b"HelloWorld"
print(data.startswith(b"Hello")) # True
print(data.endswith(b"World")) # True
replace(old, new[, count])
oldをnewに置き換えます。countを指定すれば、その回数だけ置き換えが行われます。
data = b"abcabc"
replaced = data.replace(b"abc", b"xyz")
print(replaced) # b"xyzxyz"
なお、bytearrayの場合はこのほかにリストに似た操作(append, extend, insert, popなど)も利用できます。可変である利点を活かして、バイト列を柔軟に操作したい場合に使うと便利です。
注意点
エンコード・デコードの正しい扱い
文字列として読み書きすべきデータと、バイナリとして扱うデータを区別しましょう。
- 文字情報はエンコード・デコードを使い、strとbytesを明確に分ける
- 画像や動画などバイナリフォーマットのファイルは基本的にbytesやbytearrayで取り扱う
ファイル入出力のモード
Pythonでファイルを扱う際は、テキストモード(“t”)とバイナリモード(“b”)を使い分けます。
# テキストモード
with open("sample.txt", "rt", encoding="utf-8") as f:
text = f.read()
# バイナリモード
with open("image.png", "rb") as f:
img_data = f.read() # bytesオブジェクト
バイナリデータを扱うときは”b”を必ず指定しないと、不要な文字コード変換などが行われることがあります。
大きなデータとパフォーマンス
大容量データの操作を頻繁に行う場合は、何度も連結や置き換えをするbytesよりもbytearrayやio.BytesIOなどを使った方が効率的です。
bytesは読み取り専用として、bytearrayは書き換えが必要な場合として使い分けるとよいでしょう。
まとめ
- bytesは不変のバイト列、bytearrayは可変のバイト列を扱う型
- バイナリを頻繁に書き換えるならbytearray、単純に読み込んで処理する場合はbytes
- ファイル操作時には”rb”や”wb”などバイナリモードを使用する
- 文字列との変換はエンコード・デコードを明確に区別する
- 大容量データのパフォーマンスやセキュリティ面などを意識しながら使い分ける
bytesやbytearrayの操作方法や特徴を理解することで、ファイル読み書きから通信データの解析まで、多様な場面でより柔軟にPythonを活用できるようになります。ぜひ実際のコードで試しながら慣れていってください。