Python2.x/3.0のunicode内部表現について
イントロ
Python2.6/3.0共にRC版がリリースされ、正式リリースが近づいて来ました。Python3.0の大きな変更の一つが、 Python2.xのstrとunicodeがunicode文字列のstrに統合され、従来のstrの代わりにbytesを導入することで、バイト列と文字列が明確に分けられたことです。
現在、Python2.5では、unicode文字列の内部表現がucs2のものとucs4のものがあり、それぞれの間では拡張 モジュールの互換性がなくなっています。Python2.6/3.0でこの状況がどう変化するのか調べてみました。
Python2.xのunicode内部表現について
Python2.5/2.6では、configureオプションに、--enable-unicode=ucs[24] というものがあり、デフォルトでは2になっています。 また、FedoraやUbuntuの最新版に入っているPythonのパッケージは、--enable-unicode=ucs4でビルドされています。
ucs2の利点として、Java/Windows/.NET上での相互運用性があります。 Javaや.NETの文字列オブジェクトを直接にPythonのUnicode文字列にマッピングできます。 Windows用のCPythonは、wfopenに直接ucs2のunicode文字列内部表現を渡すことで、unicodeパスに 対応しています。
もちろん、UCS-2を使う以上、Windows/Java/.NETも直面してきた、BMP外の文字への対応という問題もあります。 Windows/Java/.NETは、UCS-2にサロゲートペアを加えたUTF-16に対応することで、一文字2byteの内部表現のまま BMP外の文字へ対応してきました。Python2.5も同じくサロゲートペアに対応しています。
>>> u = u"\U00020b9f" >>> print u 𠮟 >>> u u'\U00020b9f' >>> len(u) 2 >>> u[0] u'\ud842' >>> u[1] u'\udf9f'
この用に、"\U"というエスケープを用いてBMP外の文字が入ったユニコードリテラルを生成すると、内部的には2つのcode unitと して扱われ、コード変換も正常に行われます。
ただし、code pointではなくてcode unitを単位とすると、for c in u: でサロゲートペアが別々にcに格納されたりして、 文字単位の操作が面倒になります。以前、小飼弾さんが perl, python & ruby - chr() vs. Unicode perl, python & ruby - ord() vs. Unicode でご指摘されたように、unichr()/ord()も、このcode unit単位の操作となり、BMP外の文字の扱いが面倒です。
Windows APIやJava/.NETの文字列との相互運用性を気にしなくてもよいLinux上のCPythonでは、ucs2を選ぶかucs4を選ぶかは メモリ消費量と使い勝手のトレードオフになります。Fedora/Ubuntuでは、使い勝手の良いucs4が選ばれたようです。先ほどの例を ucs4のPython2.5で実行するとこうなります。
>>> u = u"\U00020b9f" >>> print u 𠮟 >>> len(u) 1 >>> u[0] u'\U00020b9f'
Python2.6でも、Python2.5の動作を踏襲しているので、状況はまったく変わりません。(Pythonは2.x=>3.xのようなメジャー バージョンアップ以外で、下位互換性がなくなるような変更は基本的に入りません) ucs2/4間で拡張モジュールのバイナリ互換性はありませんし、ucs4のPythonで 1 code unit = 1 code point を前提に 作られたコードはucs2のPythonでBMP外の文字を正常に扱えません。
Python 3.0の変更点
Python 3.0では、configureオプションが変更され、 --with-wide-unicode
になっています。
2.xに比べると、(1)unicode無しが選択できなくなった、(2)utf-16も扱えるのにconfigureオプションでucs2と書いていたのが修正された、
というだけの修正で、デフォルトでcode unitが2byteな事に変わりがありません。
>>> u = "\U00020b9f" >>> print(u) 𠮟 >>> len(u) 2 >>> u[0] '\ud842' >>> u[1] '\udf9f' >>> u 𠮟
せっかく、文字列とバイト列が分離されたのに、文字列のAPIで「文字」ではなくて「code unit」などというものが単位になって しまったのは、個人的にすごく残念に思います。 python-devメーリングリストでも、私と同じように思った人が、utf-16のPython3.0でもcode unitではなくてcode pointを単位に してほしいという要望があったのですが、却下されてしまいました。 (該当する議論が行われたスレッド)
ただし、chr/ordについては、ucs2のPythonでもBMP外文字を扱えるようになりました。
>>> chr(0x00020b9f) '𠮟' >>> ord('\U00020b9f') 134047 >>> print(hex(_)) 0x20b9f
ucs2で単位の操作をする方法ですが、今のところ標準の方法は用意されていないようです。先ほど紹介したMLのスレッドでも、 Guidoさんはこう書いています。(該当のポスト)
The one thing that may be missing from Python is things like interpretation of surrogates by functions like isalpha() and I'm okay with adding that (since those have to loop over the entire string anyway).
結論として、Python 2.6/3.0になっても、サロゲートペアへの対応は中途半端で、将来的に Javaと同じようなAPIの追加によってucs2のPythonでもBMP外の文字を含む文字列に対応する 可能性があるようです。
個人的には、これからもLinux上でucs4のPythonを利用することで、サロゲートペアへの対応は必要な時以外 考えないようにしようと思います。(だって面倒ですし…)