% [Python] Python におけるマルチバイト文字の扱いをまとめてみる
Python のマルチバイト文字の扱いって、ちょっとクセがあるというか直感に反するところがあって、理解するまでちょっと手間取った記憶があるので、この際だから簡単にまとめてみようかと思う。
完全に把握しているわけではないので、間違いとか見付けたらツッコミよろしく。
文字コード名はめんどうなので正式名称とかにこだわらずに、Python が正しいと認識してくれるなら何でも良いやというノリで書いているので、細かいところはあんまり気にしないでいただきたい。
基本事項
- Python の文字列には二種類がある (厳密には違うけど、ここではあまり関係無いので省く)
- string
- Unicode string
- 内部コードに UCS2 または UCS4 (処理系のコンパイル時に決まる) を使った文字列 (以下では面倒なので内部コードは UCS2 であると仮定して書く)
- Python3000 では文字列はこれに統一されるとか、されないとか?
文字コードに関する事
- Python においては、マルチバイト文字に関する事柄は全て Unicode string (UCS2) が基本にある
- つまり、例えば euc-jp の文字列は Python 的に見ると「euc-jp にエンコードされたバイト列」という意味
- 故に、euc-jp で表わした 'ほげ' は、UCS2 で表わした 'ほげ' を euc-jp でエンコードしたものという認識になる
- '\xa4\xdb\xa4\xb2' = u'\u307b\u3052'.encode('euc-jp')
- 逆に、euc-jp で表わした 'ほげ' をデコードすることで UCS2 の 'ほげ' になる
- '\xa4\xdb\xa4\xb2'.decode('euc-jp') = u'\u307b\u3052'
- 個人的に、慣れるまでこの対応が逆に思えて仕方がなかった
- とりあえず、string (バイト列) から Unicode string を得るには unicode 関数を使うとわかりやすい
- 上記の encode とか decode メソッドの引数 'euc-jp' を省略すると、Python のデフォルト文字コードが使われる
- Python のデフォルト文字コードは sys.getdefaultencoding() で取得可能
- 通常の Python (CPython) ではインタプリタの起動後にこれを設定するのは不可能なので、設定したいときは site-packages/sitecustomize.py を用意する→参考リンク
- ファイルオブジェクトには encoding という属性があって、これが None じゃない場合、そのファイルオブジェクトに Unicode 文字列が渡されると自動的に encoding が示す文字コードに encode される
- sys.stdout.encoding は LANG 環境変数の値から設定されているので、LANG が正しく設定されているなら print u'ほげ' とかは文字化けしない
- encoding の値が None の場合は (普通にファイルを開いた場合なんかは、そうなる) やっぱり Python デフォルトの設定が使われる
- まあ、ファイルに書き込むときは、明示的に str.encode('euc-jp') とかやっといた方が無難だと思う。
リテラルに関する事
- ソースコード中に書かれた 'hoge' という文字列リテラルは、単純に string (バイト列) としてそのまま読み込まれる
- つまり、'ほげ' が euc-jp で書かれていれば '\xa4\xdb\xa4\xb2' だし shift-jis なら '\x82\xd9\x82\xb0' になる
- ソースコード中に書かれた u'hoge' という Unicode 文字列リテラルは、ファイルの先頭に記述された encoding 指定によって UCS2 にデコードされる
- ファイルの 1 行目か 2 行目に # -*- encoding:euc-jp -*- などと書かれていて、実際に euc-jp で u'ほげ' と書かれていれば、u'\u307b\u3052' になる
- もちろん、指定と実際の文字コードがちぐはぐなら正しい Unicode 文字列にはならない (と言うか、大抵は読み込み時にエラーになるだろう)
- ファイル中に文字コードの指定が無い場合は Python のデフォルト値でデコードされる……と思ってたんだけど、今試したら嘘だった ('ascii' でデコードしようとするみたい)
- Unicode リテラルを含むソースコードには必ず encoding の指定をしよう
対話環境に関する事
- 素の対話環境の場合、sitecustomize.py と端末の文字コード設定を合わせておくことで普通に Unicode string が使える
- IPython の場合、最近のバージョンだとマルチバイト文字は utf-8 として保持するようになっている模様
- 要するに euc-jp な端末で 'ほげ' と入力しても '\xe3\x81\xbb\xe3\x81\x92' になる
- たぶん、sys.stdin.encoding に応じてごにょごにょしてるんじゃないかと (ソース読んだわけじゃないけど)
- てことで、特に設定しなくても普通に Unicode string が使えるようになっていると思われる
- ただし、上に書いたように何が何でも utf-8 になってるので、euc-jp な端末で 'ほげ'.decode('euc-jp') とか書くと謎のエラーに悩まされることに ('ほげ'.decode('utf-8') と書こう)