Documentation Python

Pythonのbytearrayは、可変長のバイト配列を扱うための組み込み型です。bytesとは異なり、作成後に要素を変更できるため、バイナリデータの動的な操作に適しています。

bytesとbytearrayの比較

特性bytesbytearray
変更可能性不変(イミュータブル)可変(ミュータブル)
要素の変更不可
メソッド読み取り専用追加・削除可能
ハッシュ可能○(辞書キーに使用可)×
用途読み取り専用データ動的なバイナリ操作

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は、バイナリデータを動的に操作する必要がある場面で非常に便利です。ファイル編集、ネットワークプロトコル、バッファ管理などで活用できます。

参考文献

円