PythonのUnicodeDecodeErrorとUnicodeEncodeErrorを理解する

Shift-JISとUTF-8のファイルを用意する

まずは、テストに使うテキストファイルを用意します。
文字コードがそれぞれShift-JISUTF-8になるようにします。

import os

filename = "日本語_sjis.txt"
file_path = os.path.join(os.getcwd(), filename)
f = open(file_path, "w", encoding="sjis")
f.write("あいうえお\n")
f.close()

filename = "日本語_utf8.txt"
file_path = os.path.join(os.getcwd(), filename)
f = open(file_path, "w", encoding="utf-8")
f.write("あいうえお\n")
f.close()

作成したファイルの文字コードを nkf コマンドで見てみます。
それぞれ、以下のようにコンソールに出力されていると思います。

$ nkf --guess 日本語_sjis.txt
Shift_JIS (LF)
$ nkf --guess 日本語_utf8.txt
UTF-8 (LF)

UnicodeDecodeErrorを理解する

“日本語_sjis.txt” を読み込みます。

import os

filename = "日本語_sjis.txt"
file_path = os.path.join(os.getcwd(), filename)
f = open(file_path, "r")
text = f.read()
f.close()

UnicodeDecodeError に出くわします。
コード6行目で、先頭のバイトがおかしいです、と指摘されています。

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x82 in position 0: invalid start byte

エラーの理由は、Python のコードで使用されるデフォルトの文字コードは UTF-8 なのに、UTF-8 では理解できないバイト列が入ってきたからです。
Python のデフォルトエンコーディングは sys.getdefaultencoding() で知ることができます。
ドキュメントは ここ です。

コンソールで Python を呼び出して、以下を打つと、”utf-8″ と出力されます。

>>> import sys
>>> print(sys.getdefaultencoding())
utf-8

Shift-JIS と UTF-8 のバイト列の違いも見ておきましょう。

text = 'あいうえお'
print(text)
print(type(text))

# str型をbytes型に変換するので、encodeする。
print(text.encode("sjis"))
print(type(text.encode("sjis")))

print(text.encode("utf-8"))
print(type(text.encode("utf-8")))

文字列 “あいうえお” のバイト列は、以下のようになっています。
Shift-JIS の場合は、こうです。

b'\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8'

UTF-8 の場合は、こうです。

b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a'

Shift-JIS のテキストファイルを読み込む場合は、Python にそのことを教えてあげれば、問題は解決します。
教える方法は、ファイルオープン時に encoding パラメータを使います。

import os

filename = "日本語_sjis.txt"
file_path = os.path.join(os.getcwd(), filename)
f = open(file_path, "r", encoding="sjis")
text = f.read()
f.close()

以上が UnicodeDecodeError の説明です。
エラーの中身が違っていても、UnicodeDecodeError の発生原理がわかっていれば、応用できると思います。

UnicodeEncodeErrorを理解する

次は UnicodeEncodeError です。
以下のコードを実行すると、UnicodeEncodeError に出くわします。

# str型からbytes型にするので、encode
print('あいうえお'.encode('utf-8'))
print(type('あいうえお'.encode('utf-8')))

print('あいうえお'.encode('sjis'))
print(type('あいうえお'.encode('sjis')))

print('あいうえお'.encode('ascii'))
print(type('あいうえお'.encode('ascii')))

‘あいうえお’.encode(‘ascii’) の部分でエラーになります。

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-4: ordinal not in range(128) 

次に、以下のコードを実行してみます。このコードはエラーにはなりません。

print('ABC'.encode('utf-8'))
print(type('ABC'.encode('utf-8')))

print('ABC'.encode('sjis'))
print(type('ABC'.encode('sjis')))

print('ABC'.encode('ascii'))
print(type('ABC'.encode('ascii')))

UnicodeEncodeError の原因は、日本語の “あいうえお” がアスキーコードに存在しないからです。
アスキーコードの詳細は Wikipedia をご覧ください。

エラー回避方法は ここ に書かれています。
本格的な対応をする場合は、codecsモジュールが必要とも書かれています。

エラー回避方法の実装コードです。

print('あいうえおABC'.encode('ascii', 'ignore'))
print(type('あいうえおABC'.encode('ascii', 'ignore')))

print('あいうえおABC'.encode('ascii', 'replace'))
print(type('あいうえおABC'.encode('ascii', 'replace')))

print('あいうえおABC'.encode('ascii', 'xmlcharrefreplace'))
print(type('あいうえおABC'.encode('ascii', 'xmlcharrefreplace')))

print('あいうえおABC'.encode('ascii', 'backslashreplace'))
print(type('あいうえおABC'.encode('ascii', 'backslashreplace')))

print('あいうえおABC'.encode('ascii', 'namereplace'))
print(type('あいうえおABC'.encode('ascii', 'namereplace')))

以下のように出力されます。

b'ABC'
<class 'bytes'>
b'?????ABC'
<class 'bytes'>
b'&#12354;&#12356;&#12358;&#12360;&#12362;ABC'
<class 'bytes'>
b'\\u3042\\u3044\\u3046\\u3048\\u304aABC'
<class 'bytes'>
b'\\N{HIRAGANA LETTER A}\\N{HIRAGANA LETTER I}\\N{HIRAGANA LETTER U}\\N{HIRAGANA LETTER E}\\N{HIRAGANA LETTER O}ABC'
<class 'bytes'>