Unicode 文字列は内部的にはコードポイントのシーケンスとして格納されます
(正確に言えば Py_UNICODE 配列です)。
Python がどのようにコンパイルされたか (デフォルトである
--enable-unicode=ucs2 かまたは
--enable-unicode=ucs4 のどちらか) によって、
Py_UNICODE は16ビットまたは32ビットのデータ型です。
Unicode オブジェクトが CPU とメモリの外で使われることになると、
CPU のエンディアンやこれらの配列がバイト列としてどのように格納されるかが
問題になってきます。Unicode オブジェクトをバイト列に変換することを
エンコーディングと呼び、バイト列から Unicode オブジェクトを再生することを
デコーディングと呼びます。どのようにこの変換を行うかには多くの異なった方法が
あります(これらの方法のこともエンコーディングと言います)。最も単純な方法は
コードポイント 0-255 をバイト 0x0
-0xff
に写すことです。
これは U+00FF
より上のコードポイントを持つ Unicode オブジェクトは
この方法ではエンコードできないということを意味します (この方法を 'latin-1'
とか 'iso-8859-1'
と呼びます)。
unicode.encode() は次のような UnicodeEncodeError
を送出することになります: "UnicodeEncodeError: 'latin-1' codec can't
encode character u'\u1234' in position 3: ordinal not in range(256)"。
他のエンコーディングの一群(charmap エンコーディングと呼ばれます)がありますが、
Unicode コードポイントの別の部分集合とこれらがどのように 0x0
-0xff
のバイトに写されるかを選んだものです。これがどのように行なわれるかを知るには、
単にたとえば encodings/cp1252.py (主に Windows で使われる
エンコーディングです) を開いてみてください。256 文字のひとつの文字列定数
がありどの文字がどのバイト値に写されるかを示しています。
上に挙げた全てのエンコーディングは Unicode に定義された65536(あるいは1114111)
あるコードポイント中256文字しかエンコードできません。全ての Unicode コードポイント
を収める単純明快な方法は、それぞれのコードポイントを二つの引き続くバイトに収める
ものです。二つの可能性があります。すなわちビッグエンディアンかリトルエンディアンか。
これら二つのエンコーディングはそれぞれ UTF-16-BE あるいは UTF-16-LE と呼ばれます。
欠点は、たとえば UTF-16-BE をリトルエンディアンの機械で使うときに、エンコーディング
でもデコーディングでも常に二つのバイトを交換しなければならないことです。
UTF-16 はこの問題を解消します。バイトはいつでも自然なエンディアンに従います。
これらのバイトが異なるエンディアンの CPU で読まれる時は、結局交換しない訳にはいきません。
UTF-16 のバイト列のエンディアンを検知できるようにするために、いわゆる
BOM ("Byte Order Mark") があります。Unicode 文字で言うと U+FEFF
です。
この文字は全ての UTF-16 バイト列の先頭に付加されます。この文字のバイト位置を
交換したもの (0xFFFE
) は Unicode テキストに出現しないはずの違法な
文字です。そこで、UTF-16 バイト列の一文字目が U+FFFE
に見えたなら、
デコーディングの際にバイトを交換しなければなりません。不幸なことに、Unicode
4.0 までは文字 U+FEFF
には第二の目的 "ZERO WIDTH
NO-BREAK SPACE" (幅を持たず単語が分割されるのを許さない文字) がありました。
たとえばリガチャ(合字)アルゴリズムに対するヒントを与えるために使われることが
あり得ます。Unicode 4.0 になって U+FEFF
の "ZERO WIDTH NO-BREAK
SPACE" としての使用法は撤廃されました (U+2060
("WORD JOINER") に
この役割を譲りました)。しかしながら、Unicode ソフトウェアは依然として U+FEFF
の二つの役割を扱えなければなりません。一つは BOM として、エンコードされたバイトの
記憶装置上のレイアウトを決め、バイト列が Unicode 文字列にデコードされた暁には
消え去るものという役割。もう一つは "ZERO WIDTH NO-BREAK SPACE" として、
通常の文字と同じようにデコードされる文字という役割です。
さらにもう一つ Unicode 文字全てをエンコードできるエンコーディングがあり、UTF-8 と呼ばれています。UTF-8 は8ビットエンコーディングで、したがって UTF-8 には バイト順の問題はありません。UTF-8 バイト列の各バイトは二つのパートから成ります。 二つはマーカ(上位数ビット)とペイロードです。マーカは0ビットから6ビットの1の列に 0のビットが一つ続いたものです。Unicode 文字は次のようにエンコードされます (x はペイロードを表わし、連結されると一つの Unicode 文字を表わします):
範囲 | エンコーディング |
---|---|
U-00000000 ... U-0000007F |
0xxxxxxx |
U-00000080 ... U-000007FF |
110xxxxx 10xxxxxx |
U-00000800 ... U-0000FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 ... U-001FFFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-00200000 ... U-03FFFFFF |
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U-04000000 ... U-7FFFFFFF |
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
Unicode 文字の最下位ビットとは最も右にある x のビットです。
UTF-8 は8ビットエンコーディングなので BOM は必要とせず、デコードされた Unicode
文字列中の U+FEFF
は(たとえ最初の文字であったとしても)
"ZERO WIDTH NO-BREAK SPACE" として扱われます。
外部からの情報無しには、Unicode 文字列のエンコーディングにどのエンコーディングが
使われたのか信頼できる形で決定することは不可能です。どの charmap エンコーディングも
どんなランダムなバイト列でもデコードできます。しかし UTF-8 では、
任意のバイト列が許される訳ではないような構造を持っているので、
そのようなことは可能ではありません。UTF-8 エンコーディングであることを検知する
信頼性を向上させるために、Microsoft は Notepad プログラム用に UTF-8 の変種
(Python 2.5 はで "utf-8-sig"
と呼んでいます) を考案しました。
まだ Unicode 文字がファイルに書き込まれない前に UTF-8 でエンコードした BOM
(バイト列では 0xef
, 0xbb
, 0xbf
のように見えます)
を書き込んでしまいます。このようなバイト値で charmap エンコードされたファイルが
始まることはほとんどあり得ない(たとえば iso-8859-1 では
LATIN SMALL LETTER I WITH DIAERESIS
RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
INVERTED QUESTION MARK
のようになる)ので、utf-8-sig エンコーディングがバイト列から正しく推測される
確率を高めます。つまりここでは BOM はバイト列を生成する際のバイト順を決定
できるように使われているのではなく、エンコーディングを推測する助けになる印
として使われているのです。utf-8-sig codec はエンコーディングの際ファイルに
最初の3文字として 0xef
, 0xbb
, 0xbf
を書き込みます。
デコーディングの際はファイルの先頭に現れたこれら3バイトはスキップします。
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。