Pythonを学んでいると、bytesという型を見かけることがあります。
ただ、このbytesは少し分かりにくい存在です。
見た目は文字列に似ているのに、実際の使い方は異なり、普段のコードではあまり意識して使う機会も多くはありません。
そのため、「何となく分からないまま」にしてしまいがちな部分でもあります。
この記事では、bytesという型が何なのかを、コードを動かしながら少しずつ理解していきます。
Pythonの導入についてはこちら。
Python入門:最初のプログラムを作って実行する方法 – Lean Data Office
Python環境構築のやり方|初心者向け完全ガイド – Lean Data Office
bytesとは
まず、bytesを一言で表すと、「データそのもの」を扱う型です。
文字列(str)は人間が読むためのデータですが、bytesはそうではありません。
意味を持った文字ではなく、0と1の並び、つまりバイナリデータをそのまま扱うためのものです。
実際に見てみると、少しイメージがしやすくなります。
text = "hello"
print(type(text))

data = b"hello"
print(type(data))

同じ「hello」でも、まったく別の型であることが分かります。bがついている方がbytesです。
ここで重要なのは、bytesの中身は文字ではなく、単なる数値の並びとして扱われているという点です。
次に、bytesを直接作ってみます。
data = bytes(4)
print(data)

この結果を見ても、最初は少しピンと来ないかもしれません。
これは「4バイト分のデータ」を作っている状態です。\x00というのは16進数で0を表しており、それが4つ並んでいます。
つまりここでできているのは、「値がすべて0のデータ領域」であって、文字列ではありません。
こう考えると、bytesは「意味を持つ前の状態のデータ」として捉えることができます。
文字列をbytesに変換する
通常、私たちは文字列を扱うことが多いので、これをbytesに変換する場面が出てきます。
このときに使うのがencodeです。
text = "🙂"
data = text.encode("utf-8")
print(data)

この結果は、文字がバイト列に変換されたものです。
ここで大切なのは、「utf-8」という指定です。
これはエンコードのルールで、文字をどのように数値に変換するかを決めています。
同じ文字でも、このルールが変わるとバイト列の内容も変わります。
つまり、bytesだけを見ても、それが何のデータなのかは分かりません。
bytesを文字列に戻す逆に、bytesをもう一度文字として扱いたい場合は、decodeを使います。
decoded = data.decode("utf-8")
print(decoded)

ここでも先ほどと同じように、エンコードのルールを指定します。
このルールが一致していないとうまく元に戻すことができず、文字化けが起きます。
そのため、encodeとdecodeはセットで考える必要があります。
bytesの特徴とbytearray
bytesにはもう一つ重要な特徴があります。
それは、一度作ると中身を変更できないことです。
data = b"hello"
data[0] = 65

このコードはエラーになります。
これは、bytesがイミュータブル(変更不可)なオブジェクトだからです。
文字列やタプルと同じように、作成後に値を書き換えることはできません。
もし中身を変更したい場合は、bytearrayという型を使います。
data = bytearray(b"hello")
print(data)

bytearrayはbytesと似ていますが、こちらは中身を書き換えることができます。
data[0] = 72

このとき指定している「72」という値は、文字そのものではなく数値です。
バイト列では、それぞれの要素が数値として扱われています。
そのため、文字を直接扱っている感覚とは少し違う操作になります。
補足:内部の動きを見てみる
最後に、少しだけ面白い例を見てみます。
emoji = bytearray("🙂".encode("utf-8"))
print(emoji)

この状態で、最後の値を変えてみます。
emoji[3] = 0x83
print(emoji.decode("utf-8", errors="replace"))

結果は環境によって異なりますが、元の絵文字とは違う表示になります。
これは、バイト列の一部が変わったことで、「別のデータ」として解釈されるためです。
この例からも分かるように、bytesはそれ単体では意味を持たず、どのように解釈するかによって結果が変わります。
bytesは最初は扱いにくく感じるかもしれませんが、考え方自体はシンプルです。
文字列は人間のためのデータであり、bytesはコンピュータがそのまま扱うためのデータです。
そして両者は、エンコードとデコードによって行き来します。
普段はあまり意識しない部分ですが、ファイルの読み書きや通信処理では必ず登場します。
このあたりが自然に理解できるようになると、Pythonだけでなくプログラミング全体の理解も少し深まってきます。