Pythonプログラミング 〜同じように見えるが、実は違う日本語文字列の話〜

システム外部から読み込んだデータを使っていたところ、奇妙な現象が起き、しばらく理由がわかりませんました。例えば、以下のようなことが起きます。

print(word_a)
print(word_b)
print(word_a == word_b)


これを実行した結果が以下。

アジャイル
アジャイル
False

word_a にもword_bにも、”アジャイル”という文字列が格納されているように見えますが、比較してみると、同じではないと言う結果になります。
なぜこうなるのでしょう?

 理由は、Unicodeの「結合文字列」と言う特殊な文字列のせいです。
Unicodeでは、カタカナの「ジ」を表す文字コードは、’30B8’なのですが、
「シ」を表す文字コード’30B7’と、濁点の「゛」を表す’3099’を組合せて「ジ」
を表すこともできます。見た目は1文字なのですが、実際は結合された複数文字の列で表現するので「結合文字列(Combining Character Sequence)」と言う名前がついています。

 上の例の「ジ」は見た目は同じなのですが、実はword_aとword_bには、文字列としては異なるものが格納されており、このようなことが起きていました。

print(word_a.encode('unicode-escape'))
print(word_b.encode('unicode-escape'))

それぞれの文字列を文字コードを見てみると、違う文字列になっていることがわかります。

b'\\u30a2\\u30b7\\u3099\\u30e3\\u30a4\\u30eb'
b'\\u30a2\\u30b8\\u30e3\\u30a4\\u30eb'

 見た目では違いを判別できないと言うのは、なかなか厄介な問題です。
Mac OSでは、日本語のファイル名には結合文字列が使われますが、Windowsではそんなことはなかったりします。双方のOSからのデータを扱うようなソフトウェアで、データを混在させてそのまま使うと、わけがわからないことになってしまいますね。

 問題を回避するためには、前処理として「Unicodeの正規化」を行なうことが必要です。Unicode正規化とは、同じ意味の文字や文字の並びを統一的な内部表現に変換することです。例えば、半角文字と全角文字をどちらかに統一したりするのにも使われます。
unicodedataモジュールをインポートして、unicodedata.normalize()で正規化ができます。正規化の形式は四種類あります。詳細な説明は割愛しますが、ここではNFCと言う形式を使います。

word_a_n = unicodedata.normalize('NFKD', word_a)
print(word_a_n)
print(word_a_n.encode('unicode-escape'))

これを実行すると、以下のような結果となり、文字コードが変換されたのがわかります。

アジャイル
b'\\u30a2\\u30b8\\u30e3\\u30a4\\u30eb'

文字データの前処理は、結構重要と言う話でした。

この記事が気に入ったらサポートをしてみませんか?