読者です 読者をやめる 読者になる 読者になる

PythonのUnicodeDecodeError、UnicodeEncodeErrorを正しく理解する

はじめに

Pythonで日本語を扱おうとすると「UnicodeDecodeError」、「UnicodeEncodeError」に悩まされるというのをよく聞きます。
私自身もこれまではエラーが発生してもなんとなく曖昧な理解で乗り切ってきましたが、以下の記事を読んで色々と調べたら自分なりにスッキリしたので、整理した内容についてサンプルコードを交えながらまとめたいと思います。

なお、以下の説明はPython2.xを対象とした内容になっています(基本的な考え方はPython3でも同じです)

ポイント

  • Pythonの文字列型について
    • 文字列(str型)とユニコード文字列(unicode型)は別物
    • 「str型」
    • unicode型」
      • Unicodeを表現可能な型(純粋なバイト列よりも多くの情報をもった文字列形式、と考えると分かりやすい?)
  • Encode、Decodeという言葉について
    • Encode/Decodeが何から何への変換を指しているか?
      • Encode・・・「unicode型」→「str型(utf8, euc-jp, shift-jisなどなど)」に変換すること
      • Decode・・・「str型(utf8, euc-jp, shift-jisなどなど)」→「unicode型」に変換すること
  • 自分自身が利用している環境について知っておくべきこと
  • Pythonの暗黙的な文字列型変換について
    • pythonが「unicode型」と「str型」を自動的に変換する場合がある
      • エラーの大半はこれが原因
    • 自動的にDecodeされるケース
      • unicode型」と「str型」を連結、比較など(str→unicodeへの自動変換)
    • 自動的にEncodeされるケース
      • print文で標準出力・標準エラーに出力する場合など(unicode→strへの自動変換)

上にも書いたように、「UnicodeDecodeError」、「UnicodeEncodeError」の大半はPythonの暗黙的な型変換によって発生しています。Pythonが内部的に「unicode型」<->「str型」の変換を試みるものの、正しく変換出来ない場合にこれらのエラーは発生します。と、言葉で書いてもなかなか分かりづらいので、どういう場合にエラーが発生するのかについて実際のソースコード例を記載します。

なお、以下の例ではデフォルトエンコーディングが「ascii」、ロケール設定は「utf-8」、ソースコードは「utf-8」で記述しているものとします。

まずは「UnicodeDecodeError」を理解するための例を以下に記載します。

[UnicodeDecodeError編 例1] str型とstr型の連結

ソースコード
str_string1 = "日本語" #これはutf-8のstr型
str_string2 = "English" #これはutf-8のstr型

print type(str_string1)
print type(str_string2)

#str型とstr型を演算しているので、特に変換は発生せずにstr型同士が連結される
# →結果の型はstr
joined_string = str_string1 + str_string2
print type(joined_string)
print joined_string
実行結果
<type 'str'>
<type 'str'>
<type 'str'>
日本語English

[UnicodeDecodeError編 例2] unicode型とunicode型の連結

ソースコード
unicode_string1 = u"日本語" #これはunicode型
unicode_string2 = u"English" #これはunicode型
print type(unicode_string1)
print type(unicode_string2)

#unicode型とunicode型を演算しているので、特に変換は発生せずunicode型が連結される
# →結果の型はunicode型
joined_string = unicode_string1 + str_string2
print type(joined_string)
print joined_string
実行結果
<type 'unicode'>
<type 'unicode'>
<type 'unicode'>
日本語English

[UnicodeDecodeError編 例3] unicode型とstr型(デフォルトエンコーディングで扱える文字)の連結

ソースコード
unicode_string = u"日本語" #これはunicode型
str_string = "English" #これはutf-8のstr型(asciiで扱える文字)

print type(unicode_string)
print type(str_string)

#unicode型とstr型を演算しようとしているので
#暗黙の型変換によってstr型がデフォルトのエンコーディング(ascii)でデコードされる
# →utf-8の文字列「English」はasciiでデコード可能なので、問題なく文字列が結合される
joined_string = unicode_string + str_string
print type(joined_string)
print joined_string
実行結果
<type 'unicode'>
<type 'str'>
<type 'unicode'>
日本語English

[UnicodeDecodeError編 例4] unicode型とstr型(デフォルトエンコーディングで扱えない文字)の連結

ソースコード
unicode_string = u"日本語" #これはunicode型
str_string = "日本語" #これはutf-8のstr型(★asciiで扱えない)

print type(unicode_string)
print type(str_string)

#unicode型とstr型を演算しようとしているので
#暗黙の型変換によってstr型がデフォルトのエンコーディング(ascii)でデコードされる
# →utf-8の文字列「日本語」はasciiでデコード出来ないので例外発生
joined_string = str_string + unicode_string
print type(joined_string)
print joined_string
実行結果
<type 'unicode'>
<type 'str'>
Traceback (most recent call last):
File "multi_byte_string.py", line 80, in <module>
joined_string = byte_string + unicode_string
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)

UnicodeDecodeErrorにどう対処すれば良いか?

上述の問題は、プログラムの内部でunicode型とstr型が混在し、pythonが内部で暗黙的に変換を行うことによって発生しています。したがって、pythonの暗黙的な変換に頼らずにプログラマが明示的に文字列の型を意識して、変換を実施すれば問題は発生しません。
ちなみに今回の例では、ソースコード上でstr型として定義された文字列の扱いについて記載していますが、utf-8euc-jpなどのエンコーディングで作成されたファイルを扱う場合もこの方針は同様です。ファイルからデータを読み込んだ時点で、まずunicode型に変換し、プログラムの内部では基本的unicode型で処理すれば問題は発生しません。

ソースコード

問題回避するための例を以下に示します。utf-8のstr型を明示的にunicode型に変換した上で連結しています。

unicode_string = u"English" #これはunicode型
str_string = "日本語" #これはutf-8のstr型

decoded_string = unicode(str_string,"utf-8") #utf-8のstr型をunicodeにデコードする
decoded_string = str_string.decode("utf-8") #←この書き方でも同じ

joined_string = decoded_string + unicode_string
print type(joined_string)
print joined_string
実行結果
<type 'unicode'>
<type 'str'>
<type 'unicode'>
日本語English

問題なく文字列が連結されました。


次に「UnicodeEncodeError」を理解するためにprint文の振る舞いの例について以下に記載します。
print文にunicode型の文字列を渡すと、暗黙的なEncodeによって型変換が実施されます。この変換処理は出力先が端末であるか、ファイルであるかによって振る舞いが異なります。

[UnicodeEncodeError編 例1] print文による出力(端末に出力)

ソースコード
unicode_string = u"日本語" #これはunicode型
print unicode_string #unicode型をprint文に渡すとロケール設定(utf-8)でエンコードされる

# 「日本語」はutf-8でエンコード出来るので問題なし
$python multi_byte_string.py
実行結果
日本語

[UnicodeEncodeError編 例2] print文による出力(ファイルへのリダイレクト)

ソースコード
unicode_string = u"日本語" #これはunicode型
print unicode_string #unicode型をprint文に渡すとデフォルトエンコーディング(ascii)でエンコードされる

# 「日本語」はasciiでエンコード出来ないので例外発生
$python multi_byte_string.py > result.txt
実行結果
Traceback (most recent call last):
  File "multi_byte_string.py", line 101, in <module>
    print unicode_string
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

UnicodeEncodeErrorにどう対処すれば良いか?

  • printなどの暗黙的なEncode処理を伴う処理にはunicode型を渡さない(str型に変換してから渡す)
    • 暗黙的なEncodeが実施されないようにすればUnicodeEncodeErrorは発生しません
  • 標準出力をラップする
    import sys, codecs
    sys.stdout = codecs.getwriter("utf-8")(sys.stdout)


以上、UnicodeDecodeErrorとUnicodeEncodeErrorについて簡単な例を交えつつまとめました。pythonでの日本語の扱いは一見ややこしいですが、理解してしまえばそこまで複雑な話ではないと思います。より詳しい内容については以下のページなどが参考になると思います。