Python2とPython3のstr型/unicode型/bytes型/encode/decodeの違い解説

# **[+]Python2**

まず、Python2での挙動を確かめていく。

## str型

Python2のstr型はエンコード形式不明の文字列である。  

```python
>>> 'あ'
'\xe3\x81\x82'
```

ここではe38182になっているのでutf-8でエンコードされているとわかる。
しかし、その「utf-8でエンコードされている」という情報は持っていない。
なので、以下のようなエラーが発生する。

```python
>>> 'あ'.decode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
```

エンコード情報を持っていないが故に、「ASCIIでエンコードされてるっしょ」という決め打ちでデコードしようとした結果、失敗したのだ。

次のようにすることで、正しくデコードできる。

```python
>>> 'あ'.decode('utf-8')
u'\u3042'
```

こちらが引数で指定してあげる必要がある。

また、同じく「エンコード情報を持っていない」というのが原因で、次のようなエラーも出る。
以下のpyファイルを作成して、実際に実行させてみよう。

```python:hoge.py
hoge = 'あ'
```

```bash
$ python2 hoge.py
  File "hoge.py", line 1
SyntaxError: Non-ASCII character '\xe3' in file hoge.py on line 1, but no encoding declared;
 see http://python.org/dev/peps/pep-0263/ for details
```

このエラーは何なのだろうか。
実は、ASCIIで表すことができない文字をソースコード中で使用すると、その(ソースコード中の)文字をどのエンコード形式でエンコードすればいいのかわからず、このエラーが出る。
「ASCIIで表せないけど代替のエンコード形式教えてもらってないよ！」と怒られているのである。

`# -*- coding: utf-8 -*-`という1文を1行目もしくは2行目に書いて代替のエンコード形式を教えてあげることで、このエラーを回避できる。

```python:hoge2.py
# -*- coding: utf-8 -*-
hoge2 = 'あ'
```

```bash
$ python2 hoge2.py
```

ちなみに、Python3ではデフォルトのcodingがutf-8になっているため、この対応は必要なくなる。

次に、str型にindexを指定した場合を見てみる。

```python
>>> 'a'[0]
'a'

>>> 'あ'[0]
'\xe3'

>>> 'あ'[1]
'\x81'

>>> 'あ'[2]
'\x82'
```

str型はエンコード情報を持たないため、'あ'という文字が内部的に何バイトになっているかを知る術が無い。
なので、str型にindexを指定すると、**1バイトごとに区切られてしまう**。

次のように、一度unicode型にデコードして処理したあとエンコードして元に戻してあげる必要がある。
実はASCII以外の場合は、unicode文字列のほうが扱いやすいのである。
(ASCIIは必ず1バイトなので、1バイトごとに切り分けられても問題が無い。)

```python
>>> 'あいう'.decode('utf-8')[0].encode('utf-8')
'\xe3\x81\x82'

>>> '\xe3\x81\x82' == 'あ'
True
```

## unicode型

unicode型はunicodeのコードポイントを羅列した形式の文字列である。

```python
>>> u'あ'
u'\u3042'

>>> u'あいう'
u'\u3042\u3044\u3046'
```

encodeしてみる。

```python
>>> u'あ'.encode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal not in range(128)

>>> u'あ'.encode('utf-8')
'\xe3\x81\x82'
```

これもまた、ASCII決め打ちしているので引数で指定してあげる必要があるようだ。

次に、以下のpyファイルを作成して実行してみる。

```python:fuga.py
# -*- coding: utf-8 -*-
fuga = u'あ'
print(fuga)
```

```bash
$ python2 fuga.py
あ

$ python2 fuga.py > fuga.txt
Traceback (most recent call last):
  File "fuga.py", line 3, in <module>
    print(fuga)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal not in range(128)
```

リダイレクトするとエラーになる。
これは何が原因なのだろうか。
「unicode文字列をASCIIにエンコードできません」と怒られているが、ちゃんと`# -*- coding: utf-8 -*-`って書いてるじゃないか。何故utf-8ではなくASCIIでエンコードしようとしてるんだ。と思うかもしれない。
しかし、`# -*- coding: utf-8 -*-`の意味はあくまで「ソースコード中の文字列のエンコード形式」だけである。
リダイレクト無しのターミナル直接出力でエラーにならなかったのは、実は「ターミナルのエンコード形式」を自動で取得して自動でエンコードして出力してくれたからなのである。
ターミナルのエンコード形式は次のコマンドで取得できる。

```bash
$ echo $LANG
en_US.UTF-8
```

utf-8と出力された。
つまり、ソースコード中のu'あ'はprintの際にutf-8に自動でエンコードされていたのだ。

しかし、`$ python2 fuga.py > fuga.txt`とリダイレクトするとどうだろう。
この時に重要になるのはターミナルのエンコード形式ではなく、fuga.txtのエンコード形式なのである。
なので、「リダイレクト先のfuga.txtのエンコード形式を教えてもらってないってことはASCIIかな」ということで相変わらずの決め打ちASCIIエンコードをかました結果、「ASCIIでエンコードできません」というエラーに遭遇することになる。

ここまで聞けば対処法はもうお分かりだろう。

```python:fuga2.py
# -*- coding: utf-8 -*-
fuga2 = u'あ'
print(fuga2.encode('utf-8'))
```

```bash
$ python2 fuga2.py > fuga2.txt
```

そう。ソースコード中で（エンコード可能な形式で）エンコードしてしまえば良いのだ。

### _豆知識_

PYTHONIOENCODINGという環境変数にエンコード形式を格納することで、リダイレクト時のエンコード形式を指定することができる。

```bash
$ export PYTHONIOENCODING='utf-8'
$ python2 fuga.py > fuga.txt
```

# **[+]Python3**

それでは、Python3ではどうなのだろうか。

## str型

```python
>>> 'あ'
'あ'

>>> 'あ' == u'\u3042'
True
```

Python2ではstr型は**何らかの形式でエンコードされた文字列**という定義だったため、
あくまでも「文字列」ではなく「データ」として認識されていた。
しかし、Python3では**unicode文字列**と固定されているので、**一文字を一文字として認識することができる**。
なので当然、次のようなこともstr型の状態ですんなりできる。

```python
>>> 'あいう'[0]
'あ'

>>> 'あいう'[1]
'い'

>>> 'あいう'[2]
'う'
```

エンコードするには、Python2同様encode関数を使用する。

```python
>>> 'あ'.encode()
b'\xe3\x81\x82'

>>> 'あ'.encode('utf-16')
b'\xff\xfeB0'
```

ここで出てくるのがPython3で新たに加わった**「bytes型」**である。
(ちなみに、`b'\xff\xfeB0' == b'\xff\xfe\x42\x30'`である。)

## bytes型

bytes型とは、「1バイト区切りのデータの羅列を表す型」のことである。

Python3では、どんな形式でエンコードされていようとも、エンコードされていればそれは文字ではなく「データ」として認識される。

index指定してみる。

```python
>>> 'あ'.encode('utf-8')[0]
227

>>> hex('あ'.encode('utf-8')[1])
'0x81'

>>> hex('あ'.encode('utf-8')[2])
'0x82'

>>> 'あ'.encode('utf-8')[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index out of range
```

お分かりだろうか。
bytes型にindexを指定すると1バイトごとに区切られ、更にそれは「数値」になる。

足してみる。

```python
>>> b'\x01' + b'\x02'
b'\x01\x02'
```

index指定する前はまだ「バイトデータ」のまま。
なので、文字列連結のような動作をする。

もちろん次のように、バイトデータをデコードすることでunicode文字列を生成することもできる。

```python
>>> b'\xe3\x81\x82'.decode('utf-8')
'あ'
```

# **[+]まとめ**

Python2でのencode/decodeは「str型（何らかの形式でエンコードされた文字列）」と「unicode型（unicode文字列）」を行き来するモノ。
Python3でのencode/decodeは「str型（unicode文字列）」と「bytes型（バイトデータ）」を行き来するモノ。
文字列かバイトデータかの区別だけで済むようになったため、3系では文字列周りが凄くシンプルになった。