バイト型

概要

バイト型の特徴

バイト型(bytes)はバイト列を扱うための型です。

コンピューターは2進数で動作するため、全てのデータはつきつめるとバイト列にたどり着きます。 ただ、それでは扱いにくいので文字列型やリスト型などが作られました。

バイト型はそれらの人間に扱いやすいデータではなく、生のバイトの配列を操作するための型です。 使う場面は少ないとはいえ、「テキスト以外のファイル処理」や「文字コードの変換処理」 といった低いレイヤでの処理ではバイト列の操作が必要となります。 Pythonのバイト型は「 アスキーコード 」のみが使える文字列に似ていて、 実際にバイト型の値の宣言やメソッドなどは文字列型のそれと酷似しています。

バイト型とそのメソッドを使うことで直接バイト列を操作することもできますが、 たいていはなんらかの別の処理のデータとしてバイト型のデータを使うだけです。 バイト型の詳細な理解が必要となる場面は限られます。 初心者はより抽象度が高い便利な型から学ぶのがよいです。

バイト型の基本操作

宣言

1バイトは「0から255」までの値です。 アルファベットと数値から構成されるアスキーコードは1文字1バイトですので、 アスキーコードを使ってバイト列の宣言をすることができます。

宣言をする際に通常の文字列と区別をするために、 シングルクオテーションかダブルクオテーションの前に「b」という目印をつけます。

>>> a = b'hello'
>>> print(type(a))
<class 'bytes'>

>>> b = 'hello'
>>> print(type(b))
<class 'str'>

アスキーコードに含まれない文字を使ってバイト値の宣言をすると、 エラーとなります。

>>> a = b'あいうえお'
  File "<stdin>", line 1
SyntaxError: bytes can only contain ASCII literal characters.

生成

バイト列は文字列から生成することもできます。 文字列のencodeメソッドを使うと、その文字列を指定した文字コードでバイナリ化した値を返します。 デフォルトの文字コードはUTF-8です。

UTF-8はアスキーコードを拡張した文字コードなので、 文字列がアルファベットと数値だけで構成されていればアスキーコードのバイト値と全く同じになります。

>>> a = 'hello'
>>> b = a.encode('UTF-8')
>>> print(b)
b'hello'

>>> c = 'あいうえお'
>>> d = c.encode('UTF-8')
>>> print(d)
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a'

日本語をバイト値に変換(エンコード)した際に表示されている「\xe3」などは、 1バイト(0-255)を16進数2桁(16 x 16 = 256)であらわしたものです。 「\xHH」という形式で「\x00」から「\xff」まで使えます。 10進数に直すと「0」から「16 x 15 + 15 = 255」までです。 複数バイトが日本語の1文字に対応しています。

バイト型のインスタンスでdecodeメソッドを呼び出すと、 バイナリ値を引数で与えた文字コードで解釈して、文字列を返します。

>>> a = b'hello'
>>> b = a.decode('UTF-8')
>>> print(b)
hello

>>> c = b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a'
>>> d = c.decode('UTF-8')
>>> print(d)
あいうえお

演算

バイト長の確認

>>> a = len(b'hello')
>>> print(a)
5

>>> b = 'あいうえお'.encode()
>>> c = len(b)
>>> print(c)
15

bytesとbytearrayの違い

バイト型とひとくくりにしていますが、Pythonには「 bytes型 」と「 bytearray型 」の2種類があります。 どちらもバイトデータを操作するという点は同じですが、前者は文字列のようにインスタンスが持つデータが変化せず、 後者はリストのようにデータが変わります。

bytearrayの値の作成はbytesの値をbytearray関数でキャストすることで作成できます。

>>> a = bytearray(b'hello')
>>> print(a)
bytearray(b'hello')

>>> b = bytearray('あいうえお'.encode())
>>> print(b)
bytearray(b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a')

バイト型のデータをバイト単位で操作するのであれば、bytearray型を使う必要があります。 インデックスで位置を指定して要素を「0から255までの値」で上書きすることで、bytearray型の値を更新できます。 bytesの値は文字列と同じように不変であるため、bytearrayと同じ手法で更新しようとするとエラーが発生します。

>>> a = bytearray(b'hello')
>>> a[0] = 97
>>> print(a)
bytearray(b'aello')

>>> b = b'hello'
>>> b[0] = 97
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment

ただ、要素を変更するような細かい操作をしないのであれば、両者を区別する必要性はほとんどありません。 bytesもbytearrayも使えるメソッドは同じですので、標準的に利用されるbytesのみで必要な処理はまかなえます。

>>> a = b'hello'
>>> b = a.decode('UTF-8')
>>> print(b)
hello

>>> c = bytearray(b'hello')
>>> d = c.decode('UTF-8')
>>> print(d)
hello

バイト型のメソッド

ビットとバイトの関係

電気のoff/onに相当する2進数の0/1はビットと表現されます。 2進数の「0101」は0/1が4桁あるため、4ビットです。

私達が普段慣れ親しんでいる10進数でも同じですが、数値が表現できるパターン数は「基数の桁数乗」というルールがあります。 基数は2進数や10進数の2や10のことで、1桁で何種類の数を表現できるかという値です。 2進数は「0,1」の2種類で、10進数は「0,1,2,3,4,5,6,7,8,9」の10種類です。 つまり、2進数の基数は2で、10進数の基数は10となります。

数値が表現できるパターン数は「基数の桁数乗」ということですが、 たとえば10進数2桁であれば「0 - 99」の100パターンで「10の2乗」になっています。 同様に10進数4桁であれば「0 - 9999」の10000パターンで「10の4乗」になっています。 2進数もこれと同じで、2進数2桁であれば「00 - 11」の4パターンで「2の2乗」になっています。 同様に2進数4桁であれば「0000 - 1111」の16パターンで「2の4乗」となっています。 これは「2パターン x 2パターン x 2パターン x 2パターン」と考えると分かりやすいかもしれません。

2進数4桁は16パターンですので、16進数であれば1桁で表現できます。 16進数は「0,1...8,9,A,B,C,D,E,F」と数字の0から9にアルファベットを足して16個の値を1桁で表現します。

2進数と16進数及びASCIIの関係

バイト列は「01」で表現される2進数です。 ただ、01の羅列が続く2進数は人間にとって判別がし辛いです。 たとえば「110100101100010101001010」と「110100101100010101001010」

バイト型の基本操作

宣言