Python bite: マルチバイト文字
Tagged:

Python では、 ファイル冒頭に "encoding: shift_jis" 等の記述を行うことで、 ソースファイルの文字コードを指定することが出来ます。

#!/bin/env python
# encoding: shift_jis

label = '日本語'
Python ソースの文字コード指定

これで一見動いているように見えるのですが、 それをもって「マルチバイト文字が使えている」 と安心していると咬まれます

「ソースファイルの文字コード指定が可能」であることから、 てっきり「適切な内部表現形式をもったオブジェクトに変換」 してくれるものとばかり思っていたのですが、 Python では「バイト列ベースの文字列」と「Unicode 文字列」は、 そもそも異なるクラスとして定義されており、 文字列は通常「バイト列ベースの文字列」 オブジェクトとしてインスタンシエーションされます。

そして、 「バイト列ベースの文字列」オブジェクトに、 非 ASCII 文字に相当するバイト(列)が検出された場合、 文字列操作関数やフォーマット機能は上手く機能しません。

UnicodeDecodeError: 'ascii' codec can't decode byte XXX...
エラー時メッセージの例

エラー条件をきれいに抽出できなかったので、 エラーメッセージ例を掲載しておきますが、 この「概ね動くが特定のバイト列の組み合わせで不意に動かなくなる」 という振る舞いが、 この症状で一番厄介なところです (条件抽出に成功された方は、ご一報頂ければ幸いです)。

この問題を回避するには、 「Unicode 文字列」オブジェクトとして 文字列オブジェクトをインスタンシエーションする必要があり、 それは文字列リテラルに対して前置詞的に "u" を付与することで行います。

% python
> print type('あ')
<type 'str'>
> print type(u'あ')
<type 'unicode'>
文字列オブジェクトの種別

また、 「バイト列ベースの文字列」 オブジェクトとしてインスタンシエーションされるのは、 何もソース中の文字列に限った話ではなく、 ファイル等の入力から生成された文字列オブジェクトも、 基本的には「バイト列ベースの文字列」です。

入力文字列の「Unicode 文字列」オブジェクト化は、 以下のように行います。

import codecs

# 標準入力を使用する場合:
sys.stdin = codecs.getreader('shift_jis')(sys.stdin)

# 直接ファイルを開く場合:
file = codecs.open(filename, 'r', 'shift_jis')
入力文字列の Unicode オブジェクト化

ちなみに、 「Unicode 文字列」オブジェクト化とは関係ありませんが、 標準出力(ないし標準エラー出力) に対して特定のエンコーディングを指定する場合は、 以下のような処理を行います

sys.stdout = codecs.getwriter('shift_jis')(sys.stdout)
標準出力のマルチバイト対応化