Pythonのbytearrayは、可変長のバイト配列を扱うための組み込み型です。bytesとは異なり、作成後に要素を変更できるため、バイナリデータの動的な操作に適しています。
bytesとbytearrayの比較
| 特性 | bytes | bytearray |
|---|---|---|
| 変更可能性 | 不変(イミュータブル) | 可変(ミュータブル) |
| 要素の変更 | 不可 | 可 |
| メソッド | 読み取り専用 | 追加・削除可能 |
| ハッシュ可能 | ○(辞書キーに使用可) | × |
| 用途 | 読み取り専用データ | 動的なバイナリ操作 |
bytearrayの作成
基本的な作成方法
# 空のbytearray
ba = bytearray()
print(ba) # bytearray(b'')
# サイズを指定(ゼロ初期化)
ba = bytearray(5)
print(ba) # bytearray(b'\x00\x00\x00\x00\x00')
# バイト列から作成
ba = bytearray(b'hello')
print(ba) # bytearray(b'hello')
# 数値リストから作成(0-255の整数)
ba = bytearray([72, 101, 108, 108, 111])
print(ba) # bytearray(b'Hello')
# 文字列から作成(エンコーディング指定が必要)
ba = bytearray('こんにちは', 'utf-8')
print(ba) # bytearray(b'\xe3\x81\x93\xe3\x82\x93...')
イテラブルからの作成
# range から作成
ba = bytearray(range(65, 75)) # A-J
print(ba) # bytearray(b'ABCDEFGHIJ')
# ジェネレータから作成
ba = bytearray(x * 2 for x in range(5))
print(ba) # bytearray(b'\x00\x02\x04\x06\x08')
要素へのアクセスと変更
インデックスアクセス
ba = bytearray(b'hello')
# 読み取り(整数が返る)
print(ba[0]) # 104 ('h'のASCIIコード)
print(ba[-1]) # 111 ('o'のASCIIコード)
# 変更
ba[0] = 72 # 'H'のASCIIコード
print(ba) # bytearray(b'Hello')
# 範囲外アクセスはエラー
# ba[100] = 0 # IndexError
# 0-255の範囲外はエラー
# ba[0] = 256 # ValueError: byte must be in range(0, 256)
スライス操作
ba = bytearray(b'hello world')
# スライスの読み取り(bytearrayが返る)
print(ba[0:5]) # bytearray(b'hello')
print(ba[6:]) # bytearray(b'world')
print(ba[::2]) # bytearray(b'hlowrd')
# スライスの変更
ba[0:5] = b'HELLO'
print(ba) # bytearray(b'HELLO world')
# 異なる長さのバイト列で置換
ba[6:11] = b'PYTHON'
print(ba) # bytearray(b'HELLO PYTHON')
# スライスで削除
ba[5:] = b''
print(ba) # bytearray(b'HELLO')
主要なメソッド
追加系メソッド
ba = bytearray(b'hello')
# append: 1バイト追加
ba.append(33) # '!'のASCIIコード
print(ba) # bytearray(b'hello!')
# extend: 複数バイト追加
ba.extend(b' world')
print(ba) # bytearray(b'hello! world')
# insert: 指定位置に挿入
ba.insert(0, 64) # '@'を先頭に挿入
print(ba) # bytearray(b'@hello! world')
# 演算子でも追加可能
ba += b'!'
print(ba) # bytearray(b'@hello! world!')
削除系メソッド
ba = bytearray(b'hello world')
# pop: 最後の要素を削除して返す
last = ba.pop()
print(last, ba) # 100 bytearray(b'hello worl')
# pop(index): 指定位置の要素を削除
first = ba.pop(0)
print(first, ba) # 104 bytearray(b'ello worl')
# remove: 最初に見つかった値を削除
ba = bytearray(b'hello')
ba.remove(108) # 'l'を削除(最初の1つだけ)
print(ba) # bytearray(b'helo')
# clear: 全要素を削除
ba.clear()
print(ba) # bytearray(b'')
# del文による削除
ba = bytearray(b'hello')
del ba[0]
print(ba) # bytearray(b'ello')
del ba[1:3]
print(ba) # bytearray(b'eo')
検索・カウント系メソッド
ba = bytearray(b'hello world')
# find: 最初の出現位置(見つからなければ-1)
print(ba.find(b'o')) # 4
print(ba.find(b'o', 5)) # 7(位置5以降で検索)
print(ba.find(b'x')) # -1
# index: findと同様だが見つからなければ例外
print(ba.index(b'o')) # 4
# print(ba.index(b'x')) # ValueError
# count: 出現回数
print(ba.count(b'o')) # 2
print(ba.count(b'l')) # 3
変換系メソッド
ba = bytearray(b'Hello World')
# 大文字・小文字変換
print(ba.upper()) # bytearray(b'HELLO WORLD')
print(ba.lower()) # bytearray(b'hello world')
print(ba.swapcase()) # bytearray(b'hELLO wORLD')
print(ba.capitalize()) # bytearray(b'Hello world')
print(ba.title()) # bytearray(b'Hello World')
# 置換
print(ba.replace(b'World', b'Python')) # bytearray(b'Hello Python')
# 分割
print(ba.split()) # [bytearray(b'Hello'), bytearray(b'World')]
print(ba.split(b'o')) # [bytearray(b'Hell'), bytearray(b' W'), bytearray(b'rld')]
# 結合
parts = [b'one', b'two', b'three']
print(bytearray(b', ').join(parts)) # bytearray(b'one, two, three')
エンコード・デコード
# 文字列からbytearrayへ
text = "日本語テキスト"
ba = bytearray(text, 'utf-8')
print(ba)
# bytearrayから文字列へ
decoded = ba.decode('utf-8')
print(decoded) # 日本語テキスト
# 16進数表現
ba = bytearray(b'ABC')
print(ba.hex()) # 414243
print(ba.hex(' ')) # 41 42 43(区切り文字付き)
print(ba.hex(':', 2)) # 4142:43(2バイトごとに区切り)
# 16進数からbytearrayへ
ba = bytearray.fromhex('48 65 6c 6c 6f')
print(ba) # bytearray(b'Hello')
bytesとの相互変換
# bytes → bytearray
b = b'hello'
ba = bytearray(b)
print(ba) # bytearray(b'hello')
# bytearray → bytes
ba = bytearray(b'world')
b = bytes(ba)
print(b) # b'world'
# bytearrayはbytesのメソッドをほぼ継承
ba = bytearray(b'hello')
print(ba.startswith(b'he')) # True
print(ba.endswith(b'lo')) # True
print(b'ell' in ba) # True
実践的な使用例
バイナリファイルの編集
def patch_binary_file(filepath: str, offset: int, new_bytes: bytes) -> None:
"""バイナリファイルの特定位置を書き換え"""
with open(filepath, 'rb') as f:
data = bytearray(f.read())
# 指定位置のデータを書き換え
data[offset:offset + len(new_bytes)] = new_bytes
with open(filepath, 'wb') as f:
f.write(data)
# 使用例
# patch_binary_file('data.bin', 0x100, b'\x00\x00\x00\x00')
バッファとしての使用
def read_with_buffer(filepath: str, buffer_size: int = 4096) -> bytes:
"""バッファを使用したファイル読み込み"""
result = bytearray()
with open(filepath, 'rb') as f:
while True:
chunk = f.read(buffer_size)
if not chunk:
break
result.extend(chunk)
return bytes(result)
# 効率的なバッファ管理
class CircularBuffer:
"""循環バッファの実装"""
def __init__(self, size: int):
self.buffer = bytearray(size)
self.size = size
self.head = 0
self.tail = 0
self.count = 0
def write(self, data: bytes) -> int:
"""データを書き込み、書き込んだバイト数を返す"""
written = 0
for byte in data:
if self.count >= self.size:
break
self.buffer[self.tail] = byte
self.tail = (self.tail + 1) % self.size
self.count += 1
written += 1
return written
def read(self, count: int) -> bytes:
"""指定バイト数を読み取り"""
result = bytearray()
for _ in range(min(count, self.count)):
result.append(self.buffer[self.head])
self.head = (self.head + 1) % self.size
self.count -= 1
return bytes(result)
# 使用例
buf = CircularBuffer(10)
buf.write(b'Hello')
print(buf.read(3)) # b'Hel'
ネットワークプロトコルの構築
def build_packet(command: int, payload: bytes) -> bytes:
"""シンプルなパケットを構築"""
packet = bytearray()
# ヘッダー(マジックナンバー)
packet.extend(b'\xAB\xCD')
# コマンド(1バイト)
packet.append(command)
# ペイロード長(2バイト、ビッグエンディアン)
length = len(payload)
packet.extend(length.to_bytes(2, 'big'))
# ペイロード
packet.extend(payload)
# チェックサム(簡易版:全バイトのXOR)
checksum = 0
for byte in packet:
checksum ^= byte
packet.append(checksum)
return bytes(packet)
def parse_packet(data: bytes) -> dict:
"""パケットを解析"""
if len(data) < 6:
raise ValueError("Packet too short")
if data[0:2] != b'\xAB\xCD':
raise ValueError("Invalid magic number")
command = data[2]
length = int.from_bytes(data[3:5], 'big')
payload = data[5:5 + length]
checksum = data[5 + length]
# チェックサム検証
calculated = 0
for byte in data[:-1]:
calculated ^= byte
if calculated != checksum:
raise ValueError("Checksum mismatch")
return {
'command': command,
'payload': payload
}
# 使用例
packet = build_packet(0x01, b'Hello')
print(packet.hex(' ')) # ab cd 01 00 05 48 65 6c 6c 6f XX
parsed = parse_packet(packet)
print(parsed) # {'command': 1, 'payload': b'Hello'}
ビット操作
class BitBuffer:
"""ビット単位での操作をサポートするバッファ"""
def __init__(self, size: int = 0):
self.data = bytearray(size)
def set_bit(self, position: int, value: bool = True) -> None:
"""指定位置のビットを設定"""
byte_index = position // 8
bit_index = position % 8
# 必要に応じてサイズを拡張
while byte_index >= len(self.data):
self.data.append(0)
if value:
self.data[byte_index] |= (1 << bit_index)
else:
self.data[byte_index] &= ~(1 << bit_index)
def get_bit(self, position: int) -> bool:
"""指定位置のビットを取得"""
byte_index = position // 8
bit_index = position % 8
if byte_index >= len(self.data):
return False
return bool(self.data[byte_index] & (1 << bit_index))
def toggle_bit(self, position: int) -> None:
"""指定位置のビットを反転"""
byte_index = position // 8
bit_index = position % 8
while byte_index >= len(self.data):
self.data.append(0)
self.data[byte_index] ^= (1 << bit_index)
# 使用例
bb = BitBuffer()
bb.set_bit(0)
bb.set_bit(3)
bb.set_bit(7)
print(bin(bb.data[0])) # 0b10001001
print(bb.get_bit(0)) # True
print(bb.get_bit(1)) # False
bb.toggle_bit(0)
print(bb.get_bit(0)) # False
XOR暗号化
def xor_encrypt(data: bytes, key: bytes) -> bytes:
"""シンプルなXOR暗号化"""
result = bytearray(len(data))
key_len = len(key)
for i, byte in enumerate(data):
result[i] = byte ^ key[i % key_len]
return bytes(result)
def xor_decrypt(data: bytes, key: bytes) -> bytes:
"""XOR復号(暗号化と同じ操作)"""
return xor_encrypt(data, key)
# 使用例
message = b'Hello, World!'
key = b'secret'
encrypted = xor_encrypt(message, key)
print(f"暗号化: {encrypted.hex()}")
decrypted = xor_decrypt(encrypted, key)
print(f"復号化: {decrypted.decode()}") # Hello, World!
パフォーマンス考慮
import time
# 大量追加時の比較
def benchmark_append():
n = 100000
# bytearray.extend(推奨)
start = time.perf_counter()
ba = bytearray()
for chunk in [b'x' * 100] * n:
ba.extend(chunk)
extend_time = time.perf_counter() - start
# +=演算子
start = time.perf_counter()
ba = bytearray()
for chunk in [b'x' * 100] * n:
ba += chunk
add_time = time.perf_counter() - start
print(f"extend: {extend_time:.3f}s")
print(f"+=: {add_time:.3f}s")
# 事前サイズ確保
def with_prealloc(size: int) -> bytearray:
"""サイズを事前確保"""
ba = bytearray(size)
for i in range(size):
ba[i] = i % 256
return ba
def without_prealloc(size: int) -> bytearray:
"""動的に拡張"""
ba = bytearray()
for i in range(size):
ba.append(i % 256)
return ba
まとめ
| 操作 | メソッド | 例 |
|---|---|---|
| 作成 | bytearray() | bytearray(b'hello') |
| 追加 | append(), extend() | ba.append(65) |
| 削除 | pop(), remove() | ba.pop() |
| 検索 | find(), index() | ba.find(b'x') |
| 変換 | decode(), hex() | ba.decode('utf-8') |
| bytes変換 | bytes() | bytes(ba) |
bytearrayは、バイナリデータを動的に操作する必要がある場面で非常に便利です。ファイル編集、ネットワークプロトコル、バッファ管理などで活用できます。