PythonのUnicodeDecodeErrorと戦った話

※2017/11/07追記: こちらはPython2に関する記事です。Python3の場合このエラーに悩まされることはありません。



以下のエラーと戦った話。
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)

結論だけ言うと、
str型とunicode型を混ぜるな!っていう話。

scikit-learnを使って文章をクラスタリング

今回は、以下の記事を参考にして、ある配列を中身のテキストでクラスタリングしようとして起こった。
http://blog.parosky.net/archives/2212
この記事のanalyzer(文章を単語の配列に区切る関数)を自作して、
#feature extractionの部分まで書いたのがこれ

これでテストを実行してみると

UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
  sorted_features = sorted(six.iteritems(vocabulary))
===================================================
ERROR: test...
----------------------------------------------------------------------
....

  File "/Library/Python/2.7/site-packages/sklearn/feature_extraction/text.py", line 654, in _sort_features
    sorted_features = sorted(six.iteritems(vocabulary))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
----------------------------------------------------------------------

(´・ω・`)

ちょっと昔の記事だけど、すごく参考になるからまずこの記事を読もう。
PythonのUnicodeEncodeErrorを知る - HDEラボ


とりあえず今回のエラーは
「ある文字をasciiでデコードしようとしたらできなかったよ!」と言っている。
sorted_features = sorted(six.iteritems(vocabulary)) っていう、sortしている部分からエラーがでてることから、何かと何かを比較しようとして、比較したい2つの型がちがったからデフォルトのasciiでdecodeしようとしたけど出来なかったよ!ということだろうか。

array[i].textそもそもunicode型なのになんで?って悩んでたけど、問題はここだった

basic = unicode(node.feature.split(',')[-3], 'utf-8')
if basic != u"*":
    ret.append(basic)
else:
    ret.append(node.surface) <- これ

ここだけunicode型になってない。
ここのせいでstr型 と unicode型が比較されてしまっていたみたい。

    ret.append(node.surface) の部分を
    ret.append(unicode(node.surface, 'utf-8')) 

こうして解決。くだらないミスでした。

結論

Pythonのstr と unicodeの問題はめんどくさい。

他にもdecodeする際にどんなエラーが起こりうるかまとめてみたよ。


str -> unicodeにする方法が二種類あるのも面倒。

 'ほげ'.decode('utf-8')
 unicode(’ほげ’, 'utf-8')


この記事を2,3回繰り返して読むとPython文字コードの問題について理解できる。
PythonのUnicodeEncodeErrorを知る - HDEラボ


Python3.を使うと、unicode型のほうがデフォルトになってるので、
今から文字列の処理をPythonでやる人は、Python3.を使ったらこんなに悩むことは無いと思う。
諸事情があってpython2.7以下を使ってる人はこんな風にunicode型とstr型と戦わなきゃならない時があるかもしれない。がんばろう。