PythonのUnicode文字列のエンコード・デコード周りを調べてみたよ

はじめに

Python でマルチバイトの文字列を扱うと、Non-ASCIIなんちゃらって例外が出たり、日本語が文字化けして正しく出力されなかったりするので、ちょっとまとめて調べてみました。

参考にしたのは Python リファレンスの Unicode HOWTO というページ。すげー詳しい。
Unicode HOWTO — Python 2.6ja2 documentation

文字コードの歴史

ASCII … 1968年に最初にアメリカで標準化された文字コード。0から127までの128文字ですべてのアルファベットを表現した。しかし、例えば、‘naïve’や‘café’のようなアクセントを含む単語は、きちんと表現することができなかった。

ISO … 1980年代には、多くのパーソナルコンピュータは 8-bit で、つまり 8-bit で 0-255 までの値を確保することができた。ASCII コードは 127 までだったので、一部のマシンでは 128 から 255 の値をアクセント付きの文字用に割り当てた。128-255 の間の値のいくつかは、 International Standards Organzation(ISO)の定める事実上の標準となった。

Unicode … ロシア語の KOI8 やフランス語の Latin-1 など、様々な言語の文字コードを標準化しようと、1989年代に Unicode 標準化が始まった。Unicode は 16-bit (2^16 = 65,536)の異なる値を使って文字を表現する。しかし、あとになって 16-bit でさえも世界中の文字をすべて表現することは不可能だということがわかった。なお、最新の Unicode 規格は 0-1,114,111(16進数表記で 0x10ffff, 約 111 万文字) までのより広い文字コードの幅を使っている。

エンコーディングとは

Unicode 文字列の場合、 0 から 0x10ffff までの数値であるコードポイントのシーケンスは、メモリ上でバイト (0 から 255 までの値) の組として表現される。コードポイント(code point)は整数値で、通常は16進表記で書かれる。標準的にはコードポイントは U+12ca のような表記を使って書かれ、例えば U+12ca は 0x12ca (10進表記で 4810) を意味する。 Unicode 標準は文字とコードポイントを対応させる多くのテーブルを含んでいる。

コードポイントの例:

0061    'a'; LATIN SMALL LETTER A
0062    'b'; LATIN SMALL LETTER B
0063    'c'; LATIN SMALL LETTER C
...
007B    '{'; LEFT CURLY BRACKET

バイト列を Unicode 文字列に変換する規則を エンコーディング (encoding) と呼ぶ。
例えば、Unicode 文字列を ASCII エンコーディングに変換する場合、それぞれのコードポイントに対して、

1. コードポイントが128より小さい場合、コードポイントと同じ値
2. コードポイントが128以上の場合、Unicode文字列はエンコーディングで表示できない(この場合、Python は UnicodeEncodeError例外を送出する。)

また、Latin-1(ISO-8859-1)も同様に:

1. コードポイントが256より小さい場合、コードポイントと同じ値
2. コードポイントが256以上の場合、文字列は Latin-1 にエンコードできない

プログラミングで最もよく使われる UTF-8 もエンコーディングの一つ。 UTF は “Unicode Transformation Format” からとられていて、 8 はエンコーディングに 8-bit の数字を使うことを意味する。

1. コードポイントが128より小さい場合、対応するバイト値で表現。 
2. コードポイントは128から0x7ff の間の場合、128から255までの2バイト値に変換。 
3. 0x7ff より大きいコードポイントは3か4バイト列に変換し、バイト列のそれぞれのバイトは128から255の間をとる。

Python ソースコード内の Unicode リテラル

Python のソースコード内では Unicode リテラルは 'u' または 'U' の文字を最初に付けた文字列として書かれる。Python は Unicode 文字列を任意のエンコーディングで書くことができるが、どのエンコーディングを使うかを宣言しなければならない。これは、ソースファイルの1行目または2行目に以下のコメントを含めることによってできる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

ustr = u'あいうえお'
print ustr

※この構文は Emacs のファイル固有の変数を指定する表記から影響を受けてたとのこと。 Emacs のことはよくわからないので、とりあえずおまじないだと思っておく。

上記のようなコメントを含まない場合、Python ではデフォルトエンコーディングとして ASCII が利用される。そのため、同じように実行しようとすると次のようにエラーとなる。

user:~$ python unicode.py
  File "unicode.py", line 1
SyntaxError: Non-ASCII character '\xe3' in file unicode.py on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Unicode 対応のプログラムを書くための注意点

最も重要なのは、以下の2点。

1. ソフトウェア内部の動作には Unicode 文字列のみを利用し、出力時に特定のエンコーディングに変換する。
2. テストデータにはコードポイントが 127 より大きい文字を含み、さらに 255 より大きい文字を含ませる。

その他

エンコードは、インタプリタによって行われるため、仮にコメントアウトされた日本語文字列であっても当然同じようにエラーになります。
・日本語文字列などで、文字列の前に 'u' をつけないと、Unicode型ではなく、通常の str 型になります。データ型がなんだろうと不安になったら type(str) で確認。
・よくある Python の sys モジュールを使った標準入力の文字列や、対話型モジュールを使って入力された文字列なんかもとりあえず encode, decode メソッドで utf-8 にしてしまえーってやるんじゃなくて、この辺ちゃんとパターンを調べて中の動きまで追えるようにしたいです。(要追記)