Python2のstr/unicodeとencode/decode

  • 140
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

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

用語

そもそもPythonに限らず、文字コード関連の話はややこしい。
これはたぶん用語の使い方が人によって違うから。
ここではまつもとゆきひろ コードの世界に載ってる次の定義に従う。

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

2種類の文字列

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

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

str文字列

  • '...' リテラルで生成されるオブジェクト
  • UTF-8, Shift-JISなどの符号化方式によって各文字を符号化して得られるバイトを並べたもの
  • 1文字が複数バイトで表現されることもある
  • str文字列そのものは、符号化に使われた符号化方式の情報をもたない
  • 符号化方式を知るには基本的に片っ端から試すらしい
  • 対話環境で 'あいう' と入力すると、 \x で1バイトずつ区切られたバイト列が返る
>>> 'あいう'
'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86'
  • インタプリタには文字のシーケンスではなくバイトのシーケンスとして認識されている
  • マルチバイト文字を含むstr文字列では文字数とバイト数が一致しない
  • [] によるインデックスは文字ではなくバイトを返す
>>> 'あ'[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() はバイト数を返す
>>> len('あいう')
9

unicode文字列

  • u'...' リテラルで生成されるオブジェクト
  • 文字コードUCS-2上で各文字に対応する整数を並べたもの
  • 文字に対応する整数はUCS-2によって決められるため、符号化方式の違いを意識する必要がない
  • 対話環境で u'あいう' と入力すると、 \u で1文字ずつ区切られた整数列が返る
>>> u'あいう'
u'\u3042\u3044\u3046'
  • [] によるインデックスは文字を返す
>>> u'あ'[0]
u'\u3042'
>>> u'あ'[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range
  • 組み込み関数 len() は文字数を返す
>>> len(u'あいう')
3

まとめ

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

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

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

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

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

>>> 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()リファレンスにはこう書いてある。

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

unicode()リファレンスはこう。

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 が現れたりしている。

リファレンスを見ても仕様が書いていなかったので推測になるが、str文字列に対して encode() を呼び出すと、一旦ASCII符号化方式でデコードしてから再度指定された符号化方式でエンコードをする、みたいなことが起こるのだと思う。
unicode文字列を decode() した場合はその逆(ASCII符号化方式でエンコード→指定された符号化方式でデコード)が起こると考えられる。
(間違ってたら教えて下さい)

まとめ

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

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

おわりに

Python3使おう。