Python2のstr/unicodeとencode/decode


# はじめに

Python2の文字列はややこしい。

## 用語

そもそもPythonに限らず、文字コード関連の話はややこしい。
これはたぶん用語の使い方が人によって違うから。
ここでは[まつもとゆきひろ　コードの世界](http://www.amazon.co.jp/%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8%E3%82%86%E3%81%8D%E3%81%B2%E3%82%8D-%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%B8%96%E7%95%8C%E2%80%BE%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BC%E3%83%BB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AB%E3%81%AA%E3%82%8B14%E3%81%AE%E6%80%9D%E8%80%83%E6%B3%95/dp/4822234312 "まつもとゆきひろ　コードの世界")に載ってる次の定義に従う。

| 用語 | 意味 |
|:---:|:---:|
| 文字 | 言語を視覚的に表す体系に用いられる記号 |
| グリフ | 個別の文字の字形 |
| 文字集合 | 文字コード割当の対象となる文字の集まり |
| 文字コード | 個々の文字に割り当てられた番号 |
| 文字符号化方式 | 文字コードをコンピュータ上で表現する方法 |

# ２種類の文字列

Python2には文字列が2種類ある。
ここではその2つを **str文字列** および **unicode文字列** と呼び、これらをまとめて **文字列** と呼ぶ。
公式ドキュメントでも用語があまり統一されていないので、とりあえずこう呼ぶことにする。

先に言っておくと、基本的にunicode文字列を使うべき。

## str文字列

* `'...'` リテラルで生成されるオブジェクト
* UTF-8, Shift-JISなどの符号化方式によって各文字を符号化して得られるバイトを並べたもの
* 1文字が複数バイトで表現されることもある
* str文字列そのものは、符号化に使われた符号化方式の情報をもたない
* 符号化方式を知るには基本的に[片っ端から試す](http://qiita.com/zarchis/items/3258562ebc9570fa05a3 "Python - テキストファイルのエンコーディングを自動判定して処理する - Qiita")らしい
* 対話環境で `'あいう'` と入力すると、 `\x` で1バイトずつ区切られたバイト列が返る

```pycon
>>> 'あいう'
'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
```

* インタプリタには文字のシーケンスではなくバイトのシーケンスとして認識されている
* マルチバイト文字を含むstr文字列では文字数とバイト数が一致しない
* `[]` によるインデックスは文字ではなくバイトを返す

```pycon
>>> 'あ'[0]
'\xe3'
>>> 'あ'[1]
'\x81'
>>> 'あ'[2]
'\x82'
>>> 'あ'[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range
```

* 組み込み関数 `len()` はバイト数を返す

```pycon
>>> len('あいう')
9
```

## unicode文字列

* `u'...'` リテラルで生成されるオブジェクト
* 文字コードUCS-2上で各文字に対応する整数を並べたもの
* 文字に対応する整数はUCS-2によって決められるため、符号化方式の違いを意識する必要がない
* 対話環境で `u'あいう'` と入力すると、 `\u` で1文字ずつ区切られた整数列が返る

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

* `[]` によるインデックスは文字を返す

```pycon
>>> u'あ'[0]
u'\u3042'
>>> u'あ'[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range
```

* 組み込み関数 `len()` は文字数を返す

```pycon
>>> len(u'あいう')
3
```

## まとめ

上記のように、str文字列には次の２つの欠点がある。

* どの符号化方式で符号化されているのかがわかりにくい
* 文字数を求めることが難しい

一方unicode文字列ではこのような点を気にする必要がない。
よってstr文字列ではなくunicode文字列を使うべき。

# 文字列オブジェクトの型を調べる

あるオブジェクトがstr文字列かどうか、およびunicode文字列かどうかを調べるには、 `isinstance(object, str)` および `isinstance(object, unicode)` を用いる。
`type(obejct)` によって型を調べることは推奨されていない。

```pycon
>>> isinstance(u'あいう', unicode)
True
>>> isinstance('あいう', str)
True
>>> isinstance('あいう', unicode)
False
>>> isinstance(u'あいう', str)
False
```

# 文字列オブジェクトの型を変換する

str文字列↔unicode文字列の相互変換には、str/unicodeという組み込み関数を使う方法と、encode/decodeメソッドを使う方法がある。
これも先に言っておくと、基本的に encode/decodeメソッドを使うべき。

## str/unicode

Python2には、`str()`, `unicode()`という2つの組み込み関数が存在する。

`str()`の[リファレンス](http://docs.python.jp/2.7/library/functions.html#str "2. 組み込み関数 — Python 2.7ja1 documentation")にはこう書いてある。

> str([object])
オブジェクトをうまく印字可能な形に表現したものを含む文字列を返します。
...

`unicode()`の[リファレンス](http://docs.python.jp/2.7/library/functions.html#unicode "2. 組み込み関数 — Python 2.7ja1 documentation")はこう。

> unicode([object[, encoding[, errors]]])
...
オプションのパラメータが与えられていない場合、 unicode() は str() の動作をまねます。ただし、8 ビット文字列ではなく、 Unicode 文字列を返します。
...

要は、`str()`や`unicode()`はオブジェクトを表現するようなstr文字列やunicode文字列を返すためのメソッドで、str文字列とunicode文字列を相互変換するためのものではないということ。

また、これらの関数は`__str__()`や`__unicode__()`特殊メソッドが定義されているオブジェクトについてはそれらを呼び出す仕様になっているので、その動作はオブジェクトによって異なる。

## encode/decode

文字列オブジェクトは `encode()` および `decode()` というメソッドをもつ。
このことについてはよく次のような説明がなされる。

* unicode文字列に対して`encode()`メソッドを呼び出すとstr文字列が得られる
* str文字列に対して`decode()`メソッドを呼び出すとunicode文字列が得られる

これは間違っていないが、実はstr文字列にも `encode()` メソッドが存在し、またunicode文字列にも `decode()` メソッドが存在する。
なので `'あいう'.encode()` とすると `UnicodeDecodeError` が発生するといった奇妙なことが起こる。

このような `encode()` と `decode()` の挙動を調べるため、色々な文字列・符号化方式の組み合わせについて `encode()` と `decode()` を呼び出してみた。
実験は対話環境で行った。端末の入力・出力の符号化方式はUTF-8。

例えば`'abc'`と`.encode('ascii')`の交差するマスは、`'abc'.encode('ascii')`をインタプリタに入力としたときの出力を示す。

| メソッド＼文字列 | `'abc'` | `u'abc'` | `'あいう'` | `u'あいう'` |
|:---:|:---:|:---:|:---:|:---:|
| `.encode('ascii')` | `'abc'` | `'abc'` | **エラー(1)** | エラー(3) |
| `.encode('utf-8')` | `'abc'` | `'abc'` | **エラー(1)** | `'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'` |
| `.encode('shift-jis')` | `'abc'` | `'abc'` | **エラー(1)** | `'\x82\xa0\x82\xa2\x82\xa4'` |
| `.decode('ascii')` | `u'abc'` | `u'abc'` | エラー(1) | **エラー(3)** |
| `.decode('utf-8')` | `u'abc'` | `u'abc'` | `u'\u3042\u3044\u3046'` | **エラー(3)** |
| `.decode('shift-jis')` | `u'abc'` | `u'abc'` | エラー(2) | **エラー(3)** |

* エラー(1): `UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)`
* エラー(2): `UnicodeDecodeError: 'shift_jis' codec can't decode byte 0x86 in position 8: incomplete multibyte sequence`
* エラー(3): `UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)`

この結果からわかる通り、`encode()` を呼び出しているのに `UnicodeDecodeError` が現れたり、逆に `decode()` を呼び出しているのに `UnicodeEncodeError` が現れたりしている。

[リファレンス](http://docs.python.jp/2.7/library/stdtypes.html "5. 組み込み型 — Python 2.7ja1 documentation")を見ても仕様が書いていなかったので推測になるが、str文字列に対して `encode()` を呼び出すと、一旦ASCII符号化方式でデコードしてから再度指定された符号化方式でエンコードをする、みたいなことが起こるのだと思う。
unicode文字列を `decode()` した場合はその逆（ASCII符号化方式でエンコード→指定された符号化方式でデコード）が起こると考えられる。
（間違ってたら教えて下さい）

## まとめ

str文字列とunicode文字列の間の型変換には、str/unicode組み込み関数ではなく、encode/decodeメソッドを使うべき。

encode/decodeメソッドの理解としては、str文字列にせよunicode文字列にせよ、符号化方式の指定が正しければ、 `encode()` はstr文字列を返し、 `decode()` はunicode文字列を返すと思っておけば良い。


# おわりに

Python3使おう。