PEP 623: Remove wstr from Unicode について

今週新しいPEPを作りました。 www.python.org

背景

Python 3.3からUnicodeの内部表現が変わり、文字列に含まれる最大のコードポイントから1byte(ASCII or latin1), 2byte (UCS2), 4byte (UCS4)を選ぶようになっています。 (PEP 393 Flexible Unicode Representation)

それまでのPython 2 やPython 3.2までは、Unicodeの内部表現はUTF-16UTF-32コンパイル時に決定されていました。(narrow build, wide build と呼ばれていました) この時に一文字を表すC言語の型を Py_UNICODE として、UTF-16なら16bit、UTF-32なら32bitの符号なし整数型が使われていました。

昔の内部表現を Py_UNICODE* 型で取得するAPI (PyUnicode_AsUnicodeなど) や、文字列を作成するときに先に Py_UNICODE の長さを指定してアロケートするAPI (PyUnicode_FromUnicode(NULL, length)) などを動かすために、今の実装は typedef wchar_t Py_UNICODE した上でUniocodeオブジェクトの内部に wchar_t *wstr というメンバーを持っています。

これにより、小さいASCII文字列でも64bit環境では wstr のために8バイトを消費しています。非ASCII文字列ではwchar_t (UTF-16UTF-32)にエンコードしたときの長さのために ssize_t wstr_length というメンバーもあり、合計で16バイトを消費しています。

言うまでもなく文字列(Unicode)オブジェクトはPythonで最も大量にインスタンスが生成される型の一つなので、ほとんどの文字列がASCIIだとしても1インスタンスあたり8バイトの消費はそろそろ削りたいです。

削除に向けて

残念ながらいくつかのAPIがドキュメントではDeprecatedと書かれているものの、CコンパイラーのWarningを出すようにはなっていなかったので、すぐには消せません。 実際のところ、特にWindows関連のライブラリで引数を wchar_t* で受け取るのが便利だったので、標準ライブラリの内部でもまだまだ使われてしまっていました。

コンパイラーのWarningを抑止するマクロを併用することで、これらの内部利用されているけれど消したいAPIにWarningをつけ、これは Python 3.9 にバックポートしました。 ただ、引数を解析する関数 (PyArg_ParseTupleなど) が wchar_t* を使ってる部分は関数自体をWarning対象にはできないので、実行時にPythonのDeprecationWarningを出す必要があり、これは現在ベータになっているPython 3.9にはバックポートできません。 wstr を利用している全てのAPIや動作のDeprecationができるのが3.10になります。

PEP 623では、通常のDeprecationプロセスの最短である Python 3.12でwstrを削除することを提案しています。

影響度

このプランが現実的かどうか調べるために、PyPIのダウンロード数トップ4000のパッケージから、ソースパッケージ (.tar.gzなど) を提供している物を全てダウンロードし、これらのAPIの利用状況を見てみました。

一番多かったのがCythonが生成したコードが空文字列を作るために PyUnicode_FromUnicode(NULL, 0) を使っているもので、これはもう直してもらいました。また同じくCythonがもっとレアな条件で PyUnicode_FromUnicode を使っているのも見つけ、これも報告してあります。次のCython (0.29.21) では直っていると思うので、あとはいろいろなプロジェクトが新しいCythonを使って生成したコードをリリースするのを待てばほとんどが解消されるはずです。

他に多かったのが、(Cythonが生成したコードを含めて)Python 2に対応するために #ifdef で区切ったコードで、Python 3では使われないものです。

Cython生成コード意外で問題になるコードを含むプロジェクトは多分20前後で、ほとんどは簡単なものだったので既にプルリクエストを出したり報告して修正してもらったりしています。なのでこの変更はそれほど大きい breaking change にはならないと考えています。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。