re.LOCALE、re.UNICODE のはなし

昨晩の前フリは…。

「re.LOCALE、re.UNICODE のはなし」と大風呂敷広げたようにみえるタイトルだけど、大したことは書きません。そのわりに長いので、離脱する方は今のうちにどうぞ。




































実は Python 2.7 公式ドキュメント翻訳の、re と原文最新の乖離がそれなりにあることに気付いてしまって、今最新への追従を、自分で手をあげて対応中、なの。少なくともワタシの知っているプロジェクトは最低でも 5年後まで 2.7 を使うし、5年後に決断出来なければ 10年後まで使いかねない「とてもライフサイクルの長い」プロジェクト。そんなのもあるし、ワタシ自身がそうなのでわかるのだが、 re って、数あるライブラリのドキュメントの中でも、最も頻繁にみるものなんじゃないか、と思うのね。だから気付いた、アンド、言いだしっぺ、てことで。

で、これの訳出でちょいと悩んでしまったのですよ:

then LOCALE flag takes effect first followed by the UNICODE.

当然これは「LOCALE 優先で、UNICODE は後回し」の意味なわけです。LOCALE が「takes effect first」で、それに UNICODE が続く(「UNICODE によって続かれる」と直訳から入ればわかる)、のだから。ただ、脳内で日本語にしてる過程で、どうしても意味が変質しそうになるのね。「続く」という日本語のせいで。ついつい「UNICODE に続き」と「感じて」しまうでしょう? そうすると頭混乱するわけね。「どっちが first やねん?」。

で、またしても、だが、これを Google 翻訳に頼ったのが間違いだった:

1 XXX flag takes effect first followed by the YYY.
2 3 XXXフラグは最初に続いて有効になります YYY。

ナニソレ?

1 XXX flag takes effect first, followed by the YYY.
2 3 XXXフラグは、続いて第1の効果を取り YYY。

ますますエラいことに。やるんじゃなかった…。

こうなってくるともう、「Python的に」確証するしかなく。そして、想像出来るだろうか、このことの「地獄」を。ワタシはやる前からわかったが、やってみたら「もっと」だった。






そもそも「LOCALE」と「Unicode」の歴史的な話について、先にちょっと触れておきたい。

LOCALE とは無論、「localization」のために考え出された仕組みである。このテリトリーは割と多岐にわたっていて、「メッセージの翻訳」という「全体」用途を除けば、細々と「通貨の書式」「日付時刻の書式」「アルファベット的文字集合」などといったものを「地域化」する。

ただこれ、元々は「英語とポルトガル語」のような西欧諸国向けに設計されたものであって、昔から、そして今でも、東アジア圏ではトラブルを増やすだけの頭痛の種にこそなれ、「役に立」ってきたことは、歴史上一度だってない、とワタシは思う。(翻訳除き、だよ。)

日本では「通貨表示のために LOCALE を」「日付時刻のために LOCALE を」なんて考える真面目なエンジニアなんか皆無だ。そもそも官公庁では今でも「公的には和暦でなければならない」ことが多く、こんなのどう頑張ったって LOCALE のシカケではどうにもならないし、西暦表示の慣習でさえも日本の中でかなりバラ付きがある。だからその「エンジニア」が LOCALE を知ってる/知らない、なんかまったく重視されなくて、ワタシなんかも LOCALE を全く知らないエンジニアがゴリゴリと日付時刻フォーマッタを手作りし始めても、非難しようと思ったことなど一度もない。

「真面目に考えた結果、真面目に LOCALE に取り組めない」という側面もあったことは見逃せない。つまり日本などの場合、「文字種が膨大なために 7~8bit 範囲に全文字集合が収まらない」「分かち書きしない」文化圏であることが、非常に大きい。例えば全角空白を空白と同一視することを考える。ところがそうしてしまうと、「英語圏のソフトウェア」が軒並み混乱した(非 ASCII を拒絶するので)わけであって、たったこれだけのことでさえも、全く実用とは程遠かったのである。また、「は」を「単語の切れ目」と出来ないことはこれは、誰だって理解出来るであろう。「はにわ」を「は」「にわ」と分解されたらたまったものではない。つまり文字だけで単語の切れ目など区別出来ない、日本では。

それではじゃぁ、Unicode がこの状況を打破したのだろうか? 否。これも別種の頭痛の種を増やしただけだ。LOCALE とどっちがマシか、と問われれば、「どっちもどっち」としか言えない。唯一の Unicode の功績は、ただし「デカ過ぎる」その功績とは、「ともあれ、世界中どこででも同じプログラムで動作する」ことだけは強力に促進したことであり、どんなに問題を抱えていようともう後には引き返せない。だから好きでなくても Unicode には依存するしかない。






そんなわけだ。って? そう、ワタシも例によって、これまで一度として真面目に LOCALE に取り組んだことなどなかったのである。強いて言えば、「日本語だと grep 出来なくて面倒だから勘弁してくれ」と LANG=C 祭を決め込むくらい。あるいは PostgreSQL の「サーバで LOCALE 決めやがる」アホな仕様にムカつくくらい。(サーバが文字列エンコーディング決めたらいかんよ。クライアントで制御出来るようにしろや、と。) 要するに「LOCALE の大きなお世話を殺す」こと以外で興味を持ったことなどなかった、というハナシ。

ましてや正規表現の LOCALE, UNICODE である。大抵のことは「ASCII群」を相手にするんであって、そうでない場合も、Unicode 文字を直値で u”[あ-ん]” のようにすれば大部分の用は足せるわけであって、 re.flags で制御してやろうなんて、考え「たくもなかった」。んな複雑怪奇な Unicode や LOCALE の「プロ」になんかなりとうない。






仕方ねぇなぁ、と重い腰をあげて、以下でさっそくハメられる:

 1 Python 2.7.9 (default, Dec 10 2014, 12:28:03) [MSC v.1500 64 bit (AMD64)] on win32
 2 Type "help", "copyright", "credits" or "license" for more information.
 3 >>> import locale
 4 >>> print(locale.setlocale(locale.LC_ALL, "ja_JP"))
 5 Traceback (most recent call last):
 6   File "<stdin>", line 1, in <module>
 7   File "c:\Python27\lib\locale.py", line 579, in setlocale
 8     return _setlocale(category, locale)
 9 locale.Error: unsupported locale setting
10 >>> 

え? なに?

jaもダメ、ja_JP.cp932 もダメ。なんなの、これ。あー、もう、Windows めんどくせー。

と、linux で。

zzz.py
1 # -*- coding: utf-8 -*-
2 import re
3 locale.setlocale(locale.LC_ALL, 'ja_JP.utf8')
4 print(locale.getlocale())
5 
6 print(re.match(r"\w", u"1"))
7 print(re.match(r"\w", u"1", flags=re.UNICODE))
8 print(re.match(r"\w", u"1", flags=re.LOCALE))
9 print(re.match(r"\w", u"1", flags=re.LOCALE | re.UNICODE))
1 me@host: ~$ /usr/local/bin/python2.7 zzz.py
2 ('ja_JP', 'UTF-8')
3 None
4 <_sre.SRE_Match object at 0x7f7fd19b1578>
5 None
6 None

うーん。さすが「日本の LOCALE」。先の歴史的な話からわかる通り、これまで日本人エンジニアが真面目に LOCALE に取り組んだことはないので、全角「1」は「[a-zA-Z0-9_]」の一味とみなされていない、ということだろう、たぶん。ずっとこうだったんかなぁ? システム依存もある? 試したのは RHEL 系 linux だけど。(ん、てか LOCALE って多バイト文字は範疇外だったかしら?)

マズいなぁ。ますます判然としなくなってきたぞ。「LOCALE が有効に働いている」例が見つけられんのよ。だので、「LOCALE | UNICODE」も、今ひとつ「LOCALE 優先されてるの証拠には十分」でも検証として気持ちよくない。つまり「LOCALE 的に「1」は「任意の英数文字および下線」ではない / UNICODE 的に「1」は「任意の英数文字および下線」である」ではなくて、この逆になるような例がないと、今ひとつ自信が持てない、というわけだ。






ところでここまで(気持ち悪くても)「わかった」ところで Windows に戻ってきてみて、「Windows での LOCALE」の謎が少しだけ解けた:

1 Python 2.7.9 (default, Dec 10 2014, 12:28:03) [MSC v.1500 64 bit (AMD64)] on win32
2 Type "help", "copyright", "credits" or "license" for more information.
3 >>> import locale
4 >>> locale.setlocale(locale.LC_ALL, "")
5 'Japanese_Japan.932'
6 >>> 

なにそれ、イヤン。






振る舞いだけで今ひとつ自信が持てないとなれば、残るはソースコードを解読するくらいしか残ってない。仕方ねぇなぁ…。

Modules/_sre.c内。
 1 static PyObject *
 2 sre_getlower(PyObject* self, PyObject* args)
 3 {
 4     int character, flags;
 5     if (!PyArg_ParseTuple(args, "ii", &character, &flags))
 6         return NULL;
 7     if (flags & SRE_FLAG_LOCALE)
 8         return Py_BuildValue("i", sre_lower_locale(character));
 9     if (flags & SRE_FLAG_UNICODE)
10 #if defined(HAVE_UNICODE)
11         return Py_BuildValue("i", sre_lower_unicode(character));
12 #else
13         return Py_BuildValue("i", sre_lower_locale(character));
14 #endif
15     return Py_BuildValue("i", sre_lower(character));
16 }

あらま。「優先」どころか、LOCALE が指定されてたら LOCALE しか見ないのね。訳出はちょっとこれを踏まえた方が良さそうだな。






うぐぅ、ちかりた…。






なお。「re.LOCALE」の扱いについては、Python 2.7 と Python 3.x では随分違ってます。2.7 までは(ワタシ同様に) LOCALE にブーたれる程度で別に「推奨しません」ニュアンスは高くなかったのだけど、3.x でははっきりと「推奨しない」と断言されてます。

そもそも日本で LOCALE なんか 1bit も役に立たないんだから、ワタシのように「翻訳のためにどうしても理解が必要」という迫られ方をしない限りは、興味すら持つべきじゃないよ、こんなん。時代遅れである、という空気は世界的な動向であって、これはもう止められない。おそらく今後は LOCALE の「一部」だけ生きながらえて、全体としては忘れ去られていく一方になるんだと思う。