日本語文字列コード問題まとめ

ニホンジンは文字コードにうるさい

tags:python, tips, experiment, encoding, string
created:2007-02-05T20:24:24

日本語文字列コードトラブルシュート 文字列コードに関するトラブルシューティング!

文字列コード問題との戦い

Pythonに限った話ではないのですが、 日本語を取り扱うコードを書いていると やっかいなエンコーディングトラブルに遭う事は少なくないでしょう。

エンコーディングトラブルとは

  • コンパイラ・インタプリタがソースコードを解釈できない。
  • 画面表示が化ける。
  • 意図した入力ができない。
  • エンコード・デコード時にエラーがでる。
  • 正しいファイル名のつもりなのにファイルが見つからない。
  • 出力させたファイルの中身が読めない。

などといった現象を基本としていろんな問題を引き起こします。

問題のすべては「コード変換」に発生します。

実際の文字列が何のエンコーディングで、 渡す先が何のエンコーディングを期待しているか? それらを確認して合致させるように変換をするということが基本です。

「Python日本語版が必要」というのは誤解

Python2.4以降であれば日本語のコーデックは含まれており、 Python日本語版の機能はなくても対応できることが可能になりました。

Python2.3以降日本語対応リリースはありません。 特殊な事情がない限り、あえて古いPythonを使う必要はありません。

標準はもとより、主要なモジュールはPython2.5への対応もほぼ完了しています。

文字エンコードの基礎知識

日本で使われているエンコーディングの主なものには

これらがあります。

ASCII

1バイト1文字として扱うエンコーディング。 基本的に英語圏用です。

8bitなら256種類のコードが扱えますが、 後半部分に割り当てられる文字は国によってまちまち。 後半が特定の文字であることをあまり期待できないので、 後半は使われない傾向にあります。

したがって一般にASCII文字列は7bit文字列とか呼ばれます。 7bitの場合128種類のコードが扱えます。

Pythonにおいて、asciiエンコーディングとは1文字1Byteで、 エンコード・デコードになにも手を加えません。 8bitがそのまま取り扱われます。

注釈

Pythonの文字列では、 キャラクターコード「0(ゼロ)」も特別扱いしません。 ‘hoge\0moge’も9文字の文字列として扱います。

Shift-JIS

ascii互換モードと2バイトモードとが 文字単位で切り替わるエンコーディング。

国産機の頃からの定番エンコーディングです。 2バイトで全角1文字を表します。 携帯電話なんかも多くはこのエンコーディングです。

一般の日本語Windowsユーザーのほとんどは このエンコードしか知らないと思った方がいいかも。 テキストファイルを一般の人に渡す時は このエンコーディングにしておかないと 読めねーぞ!と怒られます。

EUC-JP

Unix系で日本語サポートの主流エンコーディング じつは詳細はあまり知りません。

ISO-2022-JP

日本語メールで良く使われるエンコーディング。 じつは詳細はあまり知りません。 JISコードを7ビット符号で表すエンコーディング?

Unicode

多言語の文字セットをひとつのコードに表すエンコーディング。 これにより、ロケール設定を確認せずに文字コードを扱うことが出来るようになる。 意味づけのためのコードが統一されており、 実際のメモリ配置にいくつかの実装があるのが特徴。 1文字につき2バイトから4バイトの領域が必要。 (常用漢字については2バイトの領域でカバーできています。)

UTF-8

Unicodeの実装符号のひとつ。 ascii文字との互換性を重視した実装になっており、 7bit符号領域はASCIIと互換性をキープ。 8ビット符号の後半部分にUnicodeのコード体系を割り付けている。 一般に漢字の場合、1文字に3バイトの領域が必要になる。

Web上では多言語の取り扱いがのぞまれつつ、 ASCIIの既存コンテンツをそのまま利用することを重視しているので、 utf-8の採用が爆発的に進んでいます。

Webプログラミングにおいて、utf-8は欠かせないエンコーディングと言えるでしょう。

ShiftJISの問題

Unicodeマップの相違

Shift-JISは3つの実装があります。

shift-jis
JIS X 0221に定められたエンコーディング
cp932
古くはDOSの頃に作られたコードページ。 コード体系はshift-jisをベースに、 いくつかのasciiコードの名称が変化しています。 致命的な差異ではありません。 (コード名称は英語圏キートップの絵柄にあわせているのかな?)
mbcs
Windowsロケールに依存するマルチバイトキャラクタセット。 日本語Windowsではcp932と設定されているらしい。 Windowsロケール設定によって対応付けられるコードページが違う。

で、問題はUnicodeとのマッピングが微妙に違うのが問題。

>>> chars  = "\x81\x5f\x81\x60\x81\x61\x81\x7c\x81\x91\x81\x92\x81\xca"
>>> print unicode(chars, 'cp932').encode('mbcs','replace')
~∥-¢£¬
>>> print unicode(chars, 'mbcs').encode('cp932','replace')
~∥-¢£¬
>>> print unicode(chars, 'shift-jis').encode('cp932','replace')
~∥-¢£¬
>>> print unicode(chars, 'shift-jis').encode('mbcs','replace')
???¢£¬
>>> print unicode(chars, 'cp932').encode('shift-jis','replace')
??????
>>> print unicode(chars, 'mbcs').encode('shift-jis','replace')
??????

このようにUnicodeを経由すると、再現されないコードがいくつかあります。 Windowsでは「shift-jis」エンコーディングは使わず、 「cp932」や「mbcs」を使う方がよいでしょう。

原則は、OSに依存したデバイスを対象に入出力を行うときには OSに依存したエンコードを使おうということですね。

特殊キャラクタの扱い

例えば、Windowsのファイルパス名で「\」マークは 「パス名の区切り」というルールがあります。

また、ソースコード中の「\」マークも特殊な扱いをされます。

しかし、shift-jis系のエンコーディングでは、「\」に相当するコードが 漢字に含まれるものがあります。

―ソЫⅨ
噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃
濬畚秉綵臀藹觸軆鐔饅鷭偆砡纊犾

これらが、ソース中にある場合に問題となるコンパイラもあります。 Pythonでは文字列やコメントに入れる分には問題がない様です。

これらの問題への対処

Shift-JISだけでもこういった問題があります。 ISO-2022-JPやEUC-JPなどでもこういった問題がきっとあるのでしょう。

しかし、以下の点をきっちり守れば大丈夫!

エンコーディングの一致

入力と出力のエンコーディングを一致させるという事は、 エンコードが何なのか把握しなさいということです。

把握した上で、合わせこみをしっかりすること。

  • 渡す時にエンコード・デコードを行う。
  • 出力と入力で同じエンコーディングをあらかじめ設定する。

これらのどちらかをしっかり意識することが大切です。

切り出し、検索・置き換えをしない

strオブジェクトにascii以外のコードが含まれている場合、 切り出し、検索・置き換えをしてはいけません。

例:

>>> import os
>>> folder, fname = os.path.split('hoge\\参考表.txt')
>>> print folder
hoge\参考
>>> print fname
.txt

「hoge」「参考表.txt」にならなければ困りますよね? 当然reによるマッチングでもNGです。

切り出しはエンコーディングを十分に知っておかないと、 切り離してはいけないバイト列が分断される可能性があります。 逆に追加系の処理はトラブルになりません。

加工時は必ずunicodeオブジェクトにする

基本はunicodeオブジェクトに変換してから各種加工を行うようにしてください。

Pythonの標準モジュールの文字列処理系は ほぼunicodeオブジェクトをサポートしています。

パス名処理関連は、パラメータをunicodeオブジェクトにすることで、 返値もunicodeオブジェクトになるようになっています。

>>> import os
>>> os.listdit(u'.')
['\x82\xd9\x82\xb0.txt', '\x82\xe0\x82\xb0.txt']
>>> os.listdit(u'.')
[u'\u307b\u3052.txt', u'u3082\u3052.txt']

パラメータを持たないメソッドの「os.getcwd」には代替で「os.getcwdu」が あります。できるかぎりこちらを使うようにしてください。

>>> import os
>>> os.getcwd()
'C:\\Documents and Settings\\Administrator\\\x83f\x83X\x83N\x83g\x83b\x83v'
>>> os.getcwdu()
u'C:\\Documents and Settings\\Administrator\\\u30c7\u30b9\u30af\u30c8\u30c3\u30d7'

unicodeオブジェクトにしておけば、 後処理でPython標準モジュールを使っている限りトラブルになりません。

unicodeにするときは 正しいエンコーディング指定 が必ず必要です。

エンコードデコードサンプル

文字列の基本

mbcs->unicode
>>> 'もげ'.decode('mbcs')
u'\u3082\u3052'
>>> unicode('もげ','mbcs')
u'\u3082\u3052'
unicode->cp932
>>> u'もげ'.encode('cp932')
'\x82\xe0\x82\xb0'
unicode->utf-8
>>> u'もげ'.encode('utf-8')
'\xe3\x82\x82\xe3\x81\x92'

codecsモジュールの便利な機能

ファイルオブジェクトのreaderラップ
>>> import codecs
>>> fp = file('test.txt')
>>> decorded_fp = codecs.getreader('utf-8')(fp)
>>> decorded_fp.read()
u'\u3082\u3052'
ファイルオブジェクトのwriterラップ
>>> import codecs
>>> fp = file('test.txt','w')
>>> encoded_fp = codecs.getwriter('utf-8')(fp)
>>> encoded_fp.write(u'\u3082\u3052')
(ファイルにはutf-8エンコーディングで出力)
iteratableオブジェクトの一括デコード(Py2.5以降)
>>> import codecs
>>> lines = file('test.txt').readlines()
>>> lines = codecs.iterdecode(lines, 'utf-8')
>>> for line in lines:
...   print repr(line)
...
u'\u307b\u3052'
u'\u3082\u3052'
>>>
iteratableオブジェクトの一括エンコード(Py2.5以降)
>>> import codecs
>>> lines = [u'\u307b\u3052', u'\u3082\u3052']
>>> lines = codecs.iterencode(lines, 'utf-8')
>>> for line in lines:
...   print repr(line)
...
'\xe3\x81\xbb\xe3\x81\x92'
'\xe3\x82\x82\xe3\x81\x92'
>>>

エンコードの確認と設定

エンコーディングセッティングの確認と設定方法を紹介します。

ファイルシステムエンコーディング

にて調べられます。

Windowsでは、基本的に「mbcs」固定で、 実際のエンコーディングはWindowsのロケール設定で決まります。 ほかとの関連があるのでWindowsインストール以降の変更はできません。

シェルの入出力エンコーディング

コマンドラインで「chcp」と入力するとコードページ番号が確認できます。

C:\>chcp
現在のコード ページ: 932

Pythonのインタラクティブモードは このコードページでエンコーダを自動的に設定するようです。

import sys
sys.stdin.encode
sys.stdout.encode

で調べられます。 そんなアトリビュートないよ!というエラーなら「ascii」です。

エディタとソースコードエンコーディング

ソースコード先頭に記述するエンコーディング指定で決定します。 省略した場合、Pythonデフォルトエンコーディング設定になります。

# encoding: utf-8

このような記述を書きます。

エディタに関しては愛用のエディタのリファレンスで確認してください。 まるっきりエンコーディングに関する説明がない場合は ’mbcs’である可能性が高いです。

WindowsXPのnotepadは保存時にunicode、utf-8かansiかを選べます。 ansiはmbcsとおなじ事のようです。

ファイルやネットの入出力エンコーディング

ファイルであれば、エンコーディングを確実に知る手段は不十分です。 基本は開発者による指定が必要になります。

codecsのリーダーオブジェクトでファイルオブジェクトをラップすると 読めばunicode化した結果が得られるファイルオブジェクトとして使えます。

>>> import codecs
>>> fp = file('test.txt')
>>> str_doc = fp.read()
>>> type(str_doc)
<type 'str'>
>>> fp = codecs.getreader('utf-8')(file('test.txt'))
>>> unicode_doc = fp.read()
>>> type(unicode_doc)
<type 'unicode'>

数少ない方法はBOM付きのユニコード系エンコーディングぐらいですが、 BOMありでトラブルが起こるアプリケーションも多く、 あまりBOMが付いていることを期待できません。

ネットワーク上のデータにおいては プロトコル毎にエンコーディング指定の確認・設定方法が違います。 EMAILにいたってはフィールドごとにエンコーディングの 確認・設定が必要になっています。

「伝達する内容をパースしない」つまり中継だけであれば、 strオブジェクトを用いて読み書きすることに問題は出ません。 この時は下手にunicodeオブジェクトを利用しないでください。 「エンコードが不明」 かつ 「中継だけ」 の処理ならstrのままで。

Pythonデフォルトエンコーディング

にて調べられます。

「Pythonホームフォルダ\Lib\site-packages\sitecustomize.py」にて

import sys
sys.setdefaultencoding('utf-8')

というように記述すると設定できます。

エンコーディング設定のメリット

各種エンコーディングを一致させるとどんなメリットがあるのでしょうか?

ファイルシステム=Pythonデフォルト
ファイルパス名のstrをunicode化を伴う処理に 投げ込んでも正常に処理される。 (事前デコード処理を省略できる)
ソースコード=エディタ
ソースコード上で「u’ほげほげ’」という記述が使える。 日本語でコメントが書ける。
ソースコード=Pythonデフォルト
ソースコード上で「unicode(‘ほげほげ’)」という記述が使える。 (エンコード指定が省略できる)
ソースコード=ファイルシステム
ソースコード上でstr定数で日本語ファイルパス名を記述できる。
シェルエンコード=Pythonのstdin
ソースコード上で「u’ほげほげ’」という記述が使える。 日本語でコメントが書ける。
シェルエンコード=Pythonデフォルト
インタラクティブモードで「unicode(‘ほげほげ’)」という記述が使える。 (エンコード指定が省略できる)
シェルエンコード=ファイルシステム
インタラクティブモードで「’‘」で日本語ファイルパス名を記述できる。

このように一致させておくと、unicodeコードオブジェクトへの変換、 strオブジェクトへの変換がワンタッチでできるようになります。

utf-8の場合:

>>> unicode('ほげ')
u'\u307b\u3052'
>>> str(unicode('ほげ'))
'\xe3\x81\xbb\xe3\x81\x92'

mbcsの場合:

>>> unicode('ほげ')
u'\u307b\u3052'
>>> str(unicode('ほげ'))
'\x82\xd9\x82\xb0'

方針の選択肢

エンコーディングトラブルの少ない方針を立てて見ましょう。

3つの方針が考えられます。

初級者向け

エンコード設定をすべてmbcsに統一する。

ファイル名や日本語文書のパースには事前にunicode化し、 パースが完了したらmbcsにエンコード。

中級者向け

可能な限りエンコード設定をutf-8に統一する。

海外のモジュールを活用する時や、Webコンテンツを取り扱う時に有効です。 strのままファイルパス名をsplitできたりと意外と使い勝手が良い。

上級者向け

エンコード設定をすべてmbcsに統一し、 極力内部表現をunicodeにそろえる。

入力系から文字列を受け取る時
入力系のエンコードにしたがってunicode化(デコード)。
出力系へ文字列を出力する時
出力系のエンコードにしたがってエンコード。

内と外の境界がわかっている人向け。 Python3000で採用される予定に最も近い方法。

unicodeを投げ込むとエラーの出る海外のモジュールはパッチを当てる。

まとめ

どの方針を選んでも、

  • ファイル名や日本語文書を加工する前にはunicodeにデコード。
  • strでファイル名や日本語文書を投げ込まなければならない場合は「mbcs」にエンコード。

という手続きは徹底しましょう。

すべて前者の対応ですめばいいのですが、英語圏のアプリやモジュールの中には unicodeで問題の出るものがありますので後者の対応が必要になります。

しかし、文字列サポートをユニコードのみにしていくと、 自然とunicodeによる問題は消えていきます。 unicode自身の問題があろうと無かろうと、ユーザーがunicodeで入力し、 unicodeのまま処理し、unicodeのまま出力されるようになれば、 コード変換を伴わないので、問題はおきなくなるでしょう。

Python3000がリリースされれば おそらく上級者向けのやり方が基本になっていくことでしょう。 入出力はほとんどが自動判定でエンコード・デコードないし ユニコードのまま入出力をすることになっていくでしょう。

注釈

現状では IronPython がそれに近い状態ですね。 IronPythonはUnicodeアプリとしてWindowsで動作するため、 すべてのAPIセットの文字列パラメータがUnicodeになります。 strオブジェクトは内部をユニコードで保持しています。 strとunicodeはおなじstrタイプで宣言されています。

IronPython 1.1b1 (1.1) on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> str is unicode
True

入出力でエンコードデコードが発生しないので、 IronPythonのほうが高速ということもありえますね。