Pythonの日本語環境まわりのノウハウ
最近、pythonの記事が増えてきたことは非常にうれしい。
そこで、自戒的な意味も含めてpythonで日本語の扱い方をまとめてみる。
間違ってたりしたらご指摘お願いします。ぺこりm(__)m
※ 実験の環境はWindows XP SP2 Python 2.4.4で行ってますが、ほかのOS環境でも同じと思う。
章構成はこんな感じで。
Pythonと文字コード
なにはともあれ文字コード
まず、日本語ならぬ文字コード処理をするためには、
USC,Unicode,UTF-8,UTF-16,EUC,Shift-JIS,JIS X 0208, JIS X 0213 などなどの違いを正しく理解していないといかんね。
(そもそも書いてるおまえが正しく認識してるのかよという話はおいといてw)
よく文字コードとかエンコード、character setは・・・とかいってるけど、
これは、Web記事から抜粋すると。。。。
http://www.kanzaki.com/docs/jcode.html#jiscode
- さらに文字コードとは、JIS X 0202によれば「文字集合を定め、かつその集合内の文字とビット組合せを1対1に関係づける、あいまいでない規則の集合」ということになります(JISでは「符号化文字集合」と呼んでいます)。
とのことなので、ざっくりと以下の解釈でOKだと思う。
- エンコード(符号化)
- UTF-8、Shift-JIS、EUC,JIS(ISO-2022-JP)
- キャラクターセット(文字集合)
- Unicode、ISO-2022、JIS X 0208、JIS X 0213
というわけで、一般に"文字コード"、"文字化け"といってるのは、1つめのエンコードのことさしてる。
(余談)
わかりやすくいうと同じエンコード(Shift-JIS)を利用しているWindows XPでも、
初期のバージョンはJIS X 0208でJIS X 0213のアップデートを当てると文字が変わる。
これは同一のエンコードをしていても、キャラクターセットが異なるから。
あとはこの順で読めばおおよそ理解できると思う
知識の蛇足的にはこのあたりも必要。
Pythonのエンコーディング
pythonでascii以外の文字を取り扱うときには次の3つを意識する必要がある。(まあpythonだけじゃないけどね)
1.ソースコードのエンコーディング
まず、1.のソースコードエンコーディング(エンコード宣言)は、
Pythonのソースコードでよく見るこれ。
# -*- coding: utf-8 -*-
これは、<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />と同じ役割です。*1
つまり、「このプログラムがutf-8で記述されてますよ」っていう意味。
なので、sjisなら # -*-coding: sjis -*- となります。
ちなみに、このエンドコード宣言は以下の3つを満たせばpythonは解釈してくれます*2
最後の”文字集合をサポートしている”というのは標準エンコーディングでサポートされているということ。
つまりこれはPAYTHONPATH\Lib\encodings以下に文字コードが存在するかということ。
(余談)
標準エンコーディングを見ればわかると思うけど、
Windowsでよく使われるでShift-JISは、Pythonではsjisとcp932 は違うので注意。
cp932(ms-kanji)を使うのが無難。
これを確かめるために以下のコードを実行して実験。
%% ファイルの中身 C:\Python24>type enc.py # coding=shift_jis print "helloハロー" print u"helloハロー" %% Lib\encodings\shift_jisがある場合 C:\Python24>python enc.py helloハロー helloハロー %% SHIFT_JISと大文字でもOK. Linux版でもOK %% ファイル中身 C:\Python24>type enc.py # coding=SHIFT_JIS print "helloハロー" print u"helloハロー" C:\Python24>python enc.py helloハロー helloハロー %% Lib\encodings\sjift_jisがない場合 %% shift_jis.pyをリネームして、キャッシュ(shift_jis.pyc)を消す C:\Python24>rename Lib\encodings\shift_jis.py shift_jis_bak.py C:\Python24>del lib\encodings\shift_jis.pyc C:\Python24>python enc.py File "enc.py", line 1 SyntaxError: encoding problem: with BOM
大丈夫。想定内だ。
というわけで見事に失敗で実験成功。
2.入出力する場合のエンコーディング
次に2の入出力するのエンコーディング。
これは、入出力する場所・環境によってエンコーディングは変わる
htmlならmetaに記載されているエンコーディング、
windowsのコマンドプロンプトならcp932、
Linuxならコンソールのロケール値
OS Xではutf-8 が標準かな?
ファイルならばそのファイルのエンコード。
そこで、以下の実験をして確かめる。
次のコードをエディタでshift-jisで保存(今回はenc.pyのファイル名で保存)。
(ファイルの中身)
# coding=shift_jis import sys str = "helloハロー" uni = u"helloハロー" print str print repr(str) print uni print repr(uni)
(実行結果)
C:\Python24>python enc.py helloハロー 'hello\x83n\x83\x8d\x81[' helloハロー u'hello\u30cf\u30ed\u30fc'
同じ"helloハロー"結果でも、その中身をバイト列の中身を表示してやると結果が違う。
大丈夫。想定内だ。
これは、Pythonでは内部の文字列は次の2つの形式のどちらかで保持しているから
- String
- 従来からある文字列。中身はバイト列
- Unicode String
- python2.0(1.6)からサポートされたユニコード文字列。中身はUCS2/UCS4で保存(pythonのコンパイル時のオプションで決まる)
strの「helloハロー」はバイト列、uniの「helloハロー」はunicode列。
ここでキモとなるのが、unicode列は"UCS2"で保存されているということ、
決して"UTF-8"で保存されていない。
(もちろんutf-8のバイトコードにエンコードされてるけど。これは最後の余談を参照)
なお、自分のPythonがどのUSCなのかは
import sys; sys.maxunicodeで確認できる。
USC2ならば65535、USC4なら1114111
これをUTF-8で保存して再度実行。
(実行結果)
C:\Python24>python enc.py hello繝上Ο繝シ 'hello\xe3\x83\x8f\xe3\x83\xad\xe3\x83\xbc' helloハロー u'hello\u30cf\u30ed\u30fc'
おっ? 一部文字化けしてしている。
大丈夫だ、想定内だ。
これはコマンドプロンプトのエンコード(cp932)とファイルのエンコード(utf-8)が違うため。
(ちなみに、ロケールがUTF-8なLinuxでは文字化けせずに正しく表示されます。)
(余談)
出力する端末のエンコードを調べるには、
import sys; sys.stdout.encoding とします
文字化けしないように正しくエンコード変換をしてやる。
(ファイルの中身)
# coding=utf-8 import sys str = "helloハロー" uni = u"helloハロー" uniStr = unicode(str, "utf-8", 'ignore') ## utf-8 -> unicode(UCS-2)へ変換 newStr = uniStr.encode("cp932") ## unicode(UCS-2) -> cp932へ変換 print newStr ## cp932で出力 print repr(newStr) ## cp932のバイト列かをチェック print uni print repr(uni)
(実行結果)
C:\Python24>python enc.py helloハロー 'hello\x83n\x83\x8d\x81[' helloハロー u'hello\u30cf\u30ed\u30fc'
文字化けせず出力された。
格納されているバイト列も、先ほどのShift-JISで保存したファイルの文字コードと一致して、
想定どおりの結果でオッケー。
3.デフォルトエンコーディング
さて、最後に3.デフォルトエンコーディング。
デフォルトエンコーディングとは、暗黙のエンコーディングのこと。
Pythonでは暗黙のエンコーディングがちょくちょく起こる。
例えば、先ほどから何回もでているprintでも暗黙のエンコーディングが行われている。
そもそも、暗黙のエンコーディングとはなんぞや?
これは、Pythonのシステムエンコーディングや環境や端末のエンコーディングを取得して、
自動的に適切なエンコーディングにしてくれるというもの。
例えば、codecsモジュールのcodecsオブジェクトは
open( filename, mode[, encoding[, errors[, buffering]]]) というメソッドをもってるが、
ここにある第3引数"encoding"は何もしていないと、
デフォルトエンコーディングが利用される。
Pythonでは、何も設定しないとデフォルトエンコーディングは'ascii'になっている。
デフォルトエンコーディングを取得するには
import sys; sys.getdefaultencoding() とする
デフォルトエンコーディングは、主に次の4つ場合に起こる(もちろんこれ以外にもある)。
- 標準入出力
- ファイルの入出力
- codecsモジュールでの入出力
- string文字列とユニコード文字列間の変換
(余談) pythonで利用されている各種エンコードの取得するには import sys print "encoding: " + sys.getdefaultencoding() # デフォルトのエンコード print "file system: " + sys.getfilesystemencoding() # ファイルシステムのエンコード print "stdin: " + sys.stdin.encoding # 入力端末のエンコード print "stdout: " + sys.stdout.encoding # 出力端末のエンコード
というわけで、デフォルトエンコーディングについて調べてみる。
実験コードは2.で使用したenc.pyを改変して利用する
(ファイルの中身)
# coding=utf-8 import sys print "encoding: " + sys.getdefaultencoding() print "file system: " + sys.getfilesystemencoding() print "stdin: " + sys.stdin.encoding print "stdout: " + sys.stdout.encoding print "" str = "helloハロー" uni = u"helloハロー" uniStr = unicode(str) # strのバイトコードを -> Unicode(UCS2)に変換 print uniStr print repr(uniStr) print uni print repr(uni)
(実行結果)
C:\Python24>python enc.py encoding: ascii file system: mbcs stdin: cp932 stdout: cp932 Traceback (most recent call last): File "enc.py", line 17, in ? uniStr = unicode(str) UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 5: ordinal not in range(128)
当然のエラー。
大丈夫。想定内だ。
注目したいのはここ12行目、「uniStr = unicode(str)」。
前回は、「uniStr = unicode(str, "utf-8", 'ignore')」としていた。
違いはなにか?
この2つは
- unicode(str)は、strをデフォルトエンコーディングでエンコードして、unicode(UCS2)形式で保存する。
- unicode(str, "utf-8", 'ignore')は、strを"utf-8"でエンコードして、unicode(UCS2)形式で保存する。
ということ。
結局1.は、、「ハ」は2バイトなのに、デフォルトエンコーディングがasciiなため、
1バイトのみを取ってきて、asciiエンコードしようとし、失敗したという結果。
じゃあ、毎回unicodeの時、"utf-8"を指定すればいいけど、めんどくさい。
そこで、このデフォルトエンコーディングをサイト毎に設定するのが
pythonの解説サイトの各所で書かれているsitecustomize.py。
(余談)
例えばデフォルトエンコーディングをUTF-8にするには、
pythonディレクトリのlib\site-packagesにsitecustomize.pyというファイルを作成し、
import sys
sys.setdefaultencoding('utf-8')
と記述しておく。
というわけで、デフォルトエンコーエンコーディングをutf-8にしてもう一度実行。
(実行結果)
C:\Python24>python enc.py encoding: utf-8 file system: mbcs stdin: cp932 stdout: cp932 helloハロー u'hello\u30cf\u30ed\u30fc' helloハロー u'hello\u30cf\u30ed\u30fc'
大丈夫。想定内だ。
ばっちり、UTF-8形式で保存・表示された。
ちなみに端末がcp932なのに、日本語表示されてるのは、printが適切にエンコーディングしてくれてるため。
おーけー、これで完璧!
でもちょっいまち、
ほんとに、各所ブログで書いてあるように、
安易にsitecustomize.pyを設定していいのか?
たぶん、答えは「ノー」。
だって、少なくともPython2.5まではdefaultencodingは「ascii」なのである。(Python3000では違うっぽい。これは別途)
結局、Pythonでの移植性を殺すことになる。
もちろんアップロードできるサーバがすべて、自分の管理下にある場合ならばいいけれど、
オープンソースなものやホスティングで、defaultencodingが
自分の想定しているエンコードが設定されているという保証はどこにもない。
というわけで、愚直にエンコードを指定することをおすすめする。
(余談)
あんまりおすすめしないが次のやり方すれば、Lib\以下にsitecustomize.pyを設置しなくても、
カレントフォルダにsitecustomize.pyを置けばいいので、簡単にデフォルトエンコーディングを変更出来る。import sys
import os
sys.path = [os.getcwd()] + sys.path
import sitecustomize
reload(sitecustomize)
まとめ
というわけでまとめ。
- 文字コード問題とは、多くはエンコーディング問題を指す
- pythonでは3つのエンコーディングに注意する
- Python2.4、2.5の文字列型はstring文字列(バイトコード)とUnicode文字列(UCS-2/4)に分類される。
というわけで、自戒をこめたメモですが、
お役に立ちましたでしょうか?
これであなたもLet's Pythoniグー!!! グー、グー、グーグググググググーーー
コーーーーーーーーッ!!!
って無理があったなw
(余談)
Pythonのunicodeメソッドのencode、decodeは本当にややこしい。
なんで、utf8で書かれてるファイルの文字列をstr.decode('utf-8')して格納し、
出力するときはuniStr.encode('utf-8')なのか?エンコードは符号化でデコードは復号だから、逆じゃねーの??って思う。
そこでこう考える。
str.decode('utf-8')は Pythonの内部コードUCS2へ、strのバイトコードをutf-8にデコードして格納、
str.encode('utf-8')は Pythonの内部コードUCS2から、utf-8にエンコードして出力。
つまり、処理の主体が内部(python)と考えれば納得。
追記
実はこっちのほうがよくまとまってたり
→日本語文字列コード問題まとめ