バイト文字列に対する IDLE の奇妙な振舞

2007.8.30 (鈴)

本文書は Python & IronPython 入門 の補足である。

現象

Windows 版 Python の IDLE を使っていると,バイト文字列について, 気まぐれに見える奇妙な振舞に驚かされることがある。 下図は Windows XP 上の Python 2.5.1 の IDLE を使って,UTF-8 で書かれた Python スクリプトを Run → Run Module で実行した例である。

日本語 Windows ネイティブな (より正確には,日本語用に Windows が非 Unicode 版 API の文字列エンコーディングとして表向き採用している) 文字エンコーディングは, Shift_JIS のバリアントである cp932 である。 したがって,原則からいえば,UTF-8 でスクリプトを書いた場合は, Unicode 文字列 でない限り, そのまま印字しようとすると何らかの文字化けを免れないはずである。

実際,コマンドラインから Python を実行した場合は次のようになる。

>>> import sys
>>> sys.stdout.encoding
'cp932'
>>> import hi
おはよう
縺翫・繧医≧
ごきげんよう
縺斐″縺偵s繧医≧
>>>

たとえば,Python の入門者に, Unicode 文字列ならば,どのエンコーディングのスクリプトを どの日本語環境にもっていっても画面表示できる, ということを教えるには,この振舞は期待どおりのものである。

しかるに IDLE は,「おはよう」ならば UTF-8 のバイト文字列でも正しく表示でき, 「ごきげんよう」では文字化けする,という不思議な振舞を示す。 いったい,UTF-8 のバイト文字列を表示できるのか,できないのか, まるで気まぐれのように見える。

謎解き

まず,二つの UTF-8 バイト文字列 s と t を用意する。

Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> s0 = u'おはよう'
>>> s0
u'\u304a\u306f\u3088\u3046'
>>> s = s0.encode('utf-8')
>>> s
'\xe3\x81\x8a\xe3\x81\xaf\xe3\x82\x88\xe3\x81\x86'
>>> t0 = u'ごきげんよう'
>>> t0
u'\u3054\u304d\u3052\u3093\u3088\u3046'
>>> t = t0.encode('utf-8')
>>> t
'\xe3\x81\x94\xe3\x81\x8d\xe3\x81\x92\xe3\x82\x93\xe3\x82\x88\xe3\x81\x86'
>>> 

次に,このバイト文字列を cp932 と見なして Unicode 文字列に変換してみる。

おはよう」は変換に失敗する。

>>> s.decode('cp932')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'cp932' codec can't decode bytes in position 4-5: illegal mu
ltibyte sequence
>>> 

念のため,失敗を無視して無理に変換してみる。 コマンドラインでの文字化けが再現される。こちらのほうが1文字少ないのは, 変換に失敗したバイトをはぶいたか代用文字で表示したかの違いである。

>>> s.decode('cp932', 'ignore')
u'\u7e3a\u7feb\u7e67\u533b\u2267'
>>> print s.decode('cp932', 'ignore')
縺翫繧医≧
>>> 

ごきげんよう」は変換に成功する。 つまり,cp932 文字列と見なしても, (その結果は人間にとって意味をなさないが,機械的には) 矛盾はない。 一字の違いもなく文字化けが再現される。

>>> t.decode('cp932')
u'\u7e3a\u6590\u2033\u7e3a\u5075\uff53\u7e67\u533b\u2267'
>>> print t.decode('cp932')
縺斐″縺偵s繧医≧
>>> 

この結果から,IDLE の奇妙な振舞を次のように説明できる。 バイト文字列を,まず cp932 と見なして解釈し, それで矛盾がなければ,そのままその結果を表示する (→ UTF-8 の "ごきげんよう" が文字化けする)。 そうでなければ,UTF-8 と見なして解釈し,その結果を表示する (→ UTF-8 の "おはよう" が正しく表示される)。

つまり IDLE は一種の文字コード自動判定を試みているわけである。 Windows 利用者にとって不幸なことは, バイト列を cp932 文字列と機械的に見なし得る条件が緩やかなため, この自動判定が事実上,事情を知らない人をいたずらに惑わす結果になることである。

なお,ちなみに,この例についていえば,'cp932''Shift_JIS' に置き換えても同じ結果が得られる。

アドバイス

コマンドラインとの振舞の一貫性を追求するならば, たとえ失敗しても Shift JIS で表示することが妥当だったはずである。 逆に,いわゆる Windows ネイティブを越えた広い範囲の文字を扱いたいならば, 一貫して UTF-8 を採るべきだったはずである。 IDLE を実装する Tcl/Tk の GUI はもちろん, Windows 2000 以降のコマンドラインもまた広範な Unicode 文字を扱うことができる。 最新安定版の Python 2.5.1 でも,実はまだ,このように矛盾と混乱を抱えている。 (ただし,公平のために言えば,競合する他言語で Python に匹敵する 統合的な文字エンコーディング処理機能を備えたものは,まだほとんどない)

現状,とりわけ Windows での Python 入門者にとっては, cp932 でスクリプトを書くようにすると,(他の点でマイナス要因があろうとも) さしあたり混乱させられることが少ないと言える。 IDLE では,Options → Configure IDLE... → General → Default Source Encodig を初期設定である None のままにするとよい。

もちろん,しくみを理解し,各種の選択肢について自ら判断できるならば, このアドバイスに従う必要も,こだわる必要もない。 (EUC-JP で書かれた設定ファイルに従って ISO-2022-JP のデータを処理し (Windows 上ならば) Shift_JIS で経過を出力をするスクリプトを UTF-8 で記述する, ということも Python ならば容易である)



Copyright (c) 2007 Oki Software Co., Ltd.