Pythonと日本語

Pythonで日本語扱うのは結構面倒が多いのかも知れないと思った。Pythonの問題ってよりは環境の問題って気もするけど。
どっから考えるべきか分からんけどとりあえず実験用のファイルを作る。

# yakiniku.py
print 'hello world'
print '焼肉'
print ['焼肉', 'spam']

1行目はコメント。yakiniku.pyってファイルに上の内容を保存したとする。この時ファイルのエンコーディングが何かが重要になる。
試しにcp932*1で保存して実行してみる。実行したのはWinXPコマンドプロンプト

C:\hoge\hoge\hoge>python yakiniku.py
  File "fenc.py", line 2
SyntaxError: Non-ASCII character '\x8f' in file fenc.py on line 2, but no 
encoding declared; see http://www.python.org/peps/pep-0263.html for details

(エラーメッセージの改行は俺が適当に入れた。ディレクトリ名は適当)と、エラーが出る*2。何故か。エラーメッセージを読むと、ASCIIじゃないキャラクターが入ってんだよボケが。と言われてる。当然だ。焼肉なんてのがASCIIキャラクターであるわけがない。
YAKINIKUにすれば期待通りに動く。

C:\hoge\hoge\hoge>python yakiniku.py
hello world
YAKINIKU
['YAKINIKU', 'spam']

面倒な時はこれでもよさそうだ。アルファベット使えよ、でかなりの問題はクリアできそう。
でもやっぱり日本語を使いたい場合はどうすればいいか。Pythonにそのファイルのエンコーディングが何であるかを教えてやればいい。ファイルの頭に

# coding: エンコーディング

# coding=エンコーディング

を書いてやればいいらしい。今回の場合エンコーディングにはcp932とかsjisとかその辺りを入れれば大丈夫そう。
この辺りは以下が詳しい。詳しすぎてこの記事いらんぐらい詳しい。
http://www.python.jp/Zope/articles/japanese/Python4Japanese-2


さて、ここまででも理解して実行するのは結構面倒だと思う。そもそもなんで最初の状態でASCII以外のキャラクターが入ってるとエラーになるのか。
一般的、かどうか分からないけど例えばウェブブラウザなら、まともなHPならhtml中にエンコーディングの記述があるだろうからそれに従えばいいだろう。でも世界中には書いてないページも恐らく結構ある。そういうページを開いた時ブラウザはどう動いているのか。
何も知らないで勘で書くと、なんらかの方法でエンコーディングを推測して、それをもとにページを表示してんじゃねーの?って感じ*3。少なくとも俺の知る限りエンコーディング書いてないからってエラーにはならない。ならないよね?でも推測ミスが発生する可能性があって、ミスると文字化けが起こるわけだ。
対してPythonはASCII以外の文字を使ってる時にはエンコーディングを明記してないと怒られるようになってる。推測とかは全くする気がないようだ*4。面倒だけど、推測ミスが無いやり方って事なのかもしれない。*5


話を戻して、最初のコードの頭にさっきのエンコーディングの指定を書いてもう一度コマンドプロンプトから実行してみる。

C:\hoge\hoge\hoge>python yakiniku.py
hello world
焼肉
['\x8f\xc4\x93\xf7', 'spam']

今度はちゃんと焼肉と表示された。リストの中身は焼肉になっていないが、これはそうなる事を知っていて狙って書いた。
そもそもリストをそのままprint文に書く事はそう無い気がするし、どうしても焼肉と表示させたいならループで回せばいいのでここでは置いておいておく。
内容としては、リストの最初の要素も焼肉という文字列を表している。焼肉という文字列は、cp932では\x8f\xc4\x93\xf7という表し方をする。という事のはず。WinXPコマンドプロンプトからPythonインタプリタを起動して

>>> a = '\x8f\xc4\x93\xf7'
>>> a
'\x8f\xc4\x93\xf7'
>>> print a
焼肉

とやると同じものだという事が分かるかな。print文では中身を解析して出力してくれるって事っぽい。


で、上手いこと表示できたからめでたしめでたしかってーとそうも行かない。上で上手く行ったコードをLinux*6のコンソールでそのまま書き写して実行してみる

hoge@debian:hoge/hoge/hoge$ python yakiniku.py
hello world

['\x8f\xc4\x93\xf7', 'spam']

表示されない。リストの中身は表示されてるしさっきと同じものだ。エラーが出ているわけでもない。でも焼肉とは表示されない。
多分原因はコンソールがutf-8を使うからで、print文が\x8f〜〜をutf-8で解釈した結果まともな文字にならなかったか、コンソールが\x8f〜〜をutf-8で解釈した結果まともな文字にならなかったか、そんな所なんじゃねえかなと思う*7
同じコードをutf-8で書いてエンコーディングutf-8を指定してもう1度実行すると

hoge@debian:hoge/hoge/hoge$ python yakiniku.py
hello world
焼肉
['\xe7\x84\xbc\xe8\x82\x89', 'spam']

今度はちゃんと表示される。
XPのコマンドプロンプトで同じものを実行すると

C:\hoge\hoge\hoge>python yakiniku.py
hello world
辟シ閧
['\xe7\x84\xbc\xe8\x82\x89', 'spam']

今度はこっちが化ける。コマンドプロンプトではcp932を使ってるからだ。多分。ちなみにこの\xe7〜〜ってのはutf-8での焼肉なんだろうね。


でだ、ここまででも十分日本語の扱いにくさが分かったけど、もっと最悪に使いにくい点として今までの書き方だと1バイトごとに区切られるってのがあって、どういう事かってーと

# coding=utf-8
a = 'spam'
b = '焼肉'
print len(a), a[0]
print len(b), b[0]

こんな風に書いたとする。lenってのは文字の長さとかリストの要素数とかを返す関数でa[0]ってのは要素のindex0番目を返す表記方法。でutf-8で書いたからとりあえずLinuxで実行すると

hoge@debian:hoge/hoge/hoge$ python yakiniku.py
4 s
6  

となる。spamはちゃんと思ったとおりの動きをしているが焼肉の方は1バイト単位で区切られてるから文字数とかが思ったように扱えない。


ここまでだとPythonで日本語はほぼ扱えないかやたら面倒だって事になるんだけど、流石にそれなりの解決策はある。と言ってもこれがまともに出来るようになったのは割と最近らしいけど。
今まで使ってたのがPythonの文字列型。もう1つUnicode文字列型ってのがPythonにはある。試しにcp932で書いてみると

# coding=cp932
a = u'焼肉'
print a, len(a), a[0]
print [u'焼肉', 'spam']

こんな感じ。文字列の前にuを付ける事でUnicode文字列として扱ってくれる。でXPで実行した結果が

C:\hoge\hoge\hoge>python yakiniku.py
焼肉 2 焼
[u'\u713c\u8089', 'spam']

こんな感じ。文字数もちゃんと扱えるようになる。リストの1個目の要素がさっきと違うの分かるだろうか。これがUnicode文字列での焼肉の中身だと思われる。
更にこのコードはLinux、というかutf-8を使ってるコンソールで実行しても同じ結果になる。ってことはやっぱprint文が変換してるんだろうな。コンソールで使ってるエンコーディングに。
どうも分かりにくいから書いておくと、ファイルエンコーディングUnicode文字列は全然関係ない。Unicode文字列はPythonの中でいろんな国の文字をまとめて扱う為の型って感じかな?そうハズレてないと思いたいけど正確な所は分からない。
ともかく動きとしては、Unicode文字列にしたい文字列の中身(cp932だと\x8f\xc4\x93\xf7)をエンコーディングに指定してある形式で解釈したものをPythonの中で使うUnicodeにしたのがUnicode文字列(\u713c\u8089)って感じ?だからutf-8で書いて

# coding=utf-8
a = u'焼肉'
print a, len(a), a[0]
print [u'焼肉', 'spam']

エンコーディングutf-8に指定した場合もさっきとまったく同じ結果が出る。この場合は\xe7\x84\xbc\xe8\x82\x89をutf-8で読んでUnicode文字列(\u713c\u8089)に直すって事かな?
中でどう動いてるのかはよく分からないけど、エンコーディングを指定して日本語はUnicode文字列で書くようにすればPythonの中では一意のコードで日本語が表せて、出力するときはコンソールの環境に合わせて変換してくれるっぽいって所まで分かった。
これなら心置きなくutf-8で書けるわけだ。


ちなみに、utf-8で保存したファイルのエンコーディング指定にcp932と嘘を書いてみると。

C:\hoge\hoge\hoge>python yakiniku.py
  File "fenc.py", line 2
SyntaxError: 'cp932' codec can't decode bytes in position 11-12: illegal
multibyte sequence

こうなる。デコードできなかったエラーだからたまたま同じコード使ってたりするといっちゃいそうな雰囲気だけどどうなんだろ。


1回Unicode文字列にしちゃえばあとは結構自由に使えるね

# coding=utf-8
a = u'焼肉'
L = [a.encode('cp932'), a.encode('euc-jp'), a.encode('utf-8')]
print L
for i in L:
    print i,

Unicode文字列からそれぞれのエンコーディング

C:\hoge\hoge\hoge>python yakiniku.py
['\x8f\xc4\x93\xf7', '\xbe\xc6\xc6\xf9', '\xe7\x84\xbc\xe8\x82\x89']
焼肉 セニニ・辟シ閧

みたいなこともできる。


とりあえずこんなところかな。これで大きく間違ってなければファイル内で日本語使う分にはそれほど不便は無くなるけどコンソールから入力とかはどうすんだろうな。まあ入力ぐらいアルファベットか数字で入力してもらえばいいか。日本語使うならエンコーディングを片っ端から試していってエラーでたら別のって風にやるのかな?しかしそれもどの程度信用できるのかやってみないと分からんな。

*1:shift-jisとだいたい同じ物らしい。shift-jisと読み替えても多分平気

*2:エラーが出るようになったのはバージョン2.5からっぽい?Debianに入ってる2.4ではWarningは出るけどその後無理やり実行される。

*3:あるいは書いてない場合のデフォルトを指定しておくとか?

*4:2.4だとコンソールのエンコードで無理やり読んでみるのかな?

*5:でも書く人が何のエンコーディングで書いてるのか把握してないとわけが分からなくなるやり方でもあると思う

*6:Debian etch

*7:前者っぽい?