python2.xを使い始めて、必ずと言って良いほど遭遇するのが日本語(マルチバイト)関連の問題です。
ネットで同様のケースを調べて、あまり理解をせずに、対処療法的にその場の問題を回避している人も多いように思いますが、一度腰を据えて理解すれば、それほど難しくないですし、python以外の言語にも応用ができます。
マルチバイト問題については、概念だけではなく、実際に手を動かし、目で確かめる(文字コードそのものを見る)事が重要です。
今回は、python2.xで遭遇する文字コード関連のエラーを実際に発生させ、その理由を理解した上で対処を行ってみましょう。
ケース1
[ 再現 ]
pythonスクリプトファイルのencodingをcp932にし、以下を記述します。
ustr = u'い'
[ 現象 ]
SyntaxError: Non-ASCII character '\x82' in file [file path] on line 1, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details
[ 理由 ]
ファイルの1行目にasciiコードじゃないコード\x82('い'をcp932で符号化した際の1byte目)が存在しているが、encoding宣言が存在しないのでpythonが構文解析できなかった。
[ 対処 ]
pythonスクリプトファイル内に日本語を記述する場合は、pythonが構文解析ができるようにencoding宣言をする必要がある。# coding:cp932 ustr = u'い'※2行目までにencoding宣言を記述しないと解釈されなかった
ケース2
[ 再現 ]
pythonスクリプトファイルのencodingをutf-8にし、以下を記述します。
# coding:cp932 ustr = u'い'
[ 現象 ]
SyntaxError: 'cp932' codec can't decode bytes in position 11-12 : illegal multibyte sequence
[ 理由 ]
pythonがutf-8で符号化(encode)されたスクリプトファイルのバイト文字列を、encoding宣言に指定されたcp932で復号(decode)しようとしたが、 cp932においては想定外のマルチバイトの並び(utf-8の並び)が存在したので構文解釈できなかった。
[ 対処 ]
スクリプトファイルに書きす際、符号化に使用した文字コードとencoding宣言した復号用の文字コードを一致させる事を忘れない# coding:utf-8 ustr = u'い'逆に、pythonスクリプトファイルのencodingをcp932にし、encoding宣言をutf-8にした場合は、何故か、SyntaxErrorではなくUnicodeDecodeErrorが発生する。理由は同じだがErrorの出方が変わる事は謎。
ケース3
[ 再現 ]
pythonスクリプトファイルのencodingをutf-8にし、以下を記述します。
#coding:utf-8 print u'い'
[ 現象 ]
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3044' in position 0 : ordinal not in range(128)
[ 理由 ]
printで標準出力に書きだす際にunicode型の文字列をデフォルトencodingとしてasciiで符号化(encode)しようとしたが、 unicode\u3044('い'のunicodeコードポイント)をasciiコードで符号化できなかった。 asciiには'い'のコードが存在しないので、符号化できない。
[ 対処 ]
標準出力に書き出す際に明示的に文字コードを指定して符号化する。
#coding:utf-8 print u'い'.encode('utf-8')
ケース4
[ 再現 ]
pythonスクリプトファイルのencodingをutf-8にし、以下を記述します。
# coding:utf-8 u'い' + 'ろ'
[ 現象 ]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0 : ordinal not in range(128)
[ 理由 ]
unicode型とstr型を結合しようとした時に、str型をunicode型に変換しようとする。その際にデフォルトencodingとしてasciiで復号しようとしたが、 utf8で記述された'ろ'の1バイト目の0xe3がasciiコードの範囲内じゃない為、復号に失敗した。
[ 対処 ]
str型をunicode型に変換する際に明示的に文字コードを指定して復号する。
#coding:utf-8 u'い' + 'ろ'.decode('utf-8')
上記を理解した上で、日本語を使用する際のルールを単純化すると以下のようになります。
- ファイルの文字コードと一致させたencoding宣言を行う
- str型とunicode型の間の自動変換が行われる場面では、文字コードを明示したencode,decodeを行う