UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 0: illegal multibyte sequence
なるエラーが出てしまうことがある。
$ cat sample.py # サンプルコード open("test.txt").readlines() $ python sample.py Traceback (most recent call last): File "C:\Users\user\tmp\bom\sample.py", line 3, inopen("test.txt").readlines() UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 0: illegal multibyte sequence
このエラー、0xefなんてテキストデータに入れた覚えがないのに……と混乱を招くかもしれない。 あるいは読み込めたものの、この見えないデータが変換の邪魔をして、例えばintにあとでデータを変換しようとしたときに
ValueError: invalid literal for in() with base 10: '\ueff*'
のようなエラーに化けていることもあるが、これも同じ原因である。これはテキストファイル先頭にバイトオーダーマーク (BOM) とよばれる\ueffというデータが付いてしまっているためにおこる。
解決策1 BOM付のまま読み込む
codecs.open
のようにエンコーディングを指定してファイルを開ける
ライブラリを使えば、エンコーディングとしてBOM付UTF-8に相当するutf_8_sig
を指定することでエラーを回避できる。
$ cat sample.py # サンプルコードを修正 import codecs codecs.open("test.txt", encoding="utf_8_sig").readlines() $ python sample.py # エラーが出ない
解決策2 BOM削除して読み込む
もし入力ファイル自体ををいじることが許可されているなら、 BOMを消したファイルを作ってしまうのも手っ取り早い。やり方はエディタを使うなどいろいろあるが、コマンドライン上ではvi/vim のバイナリモードを使うのが簡便だ。 テキストモードで開くとBOMが隠蔽されてしまうので-bオプションを付けバイナリモードで開く。
$ vim -b test.txt <feff> test # この"<feff>"がbomなので消す $ cat sample.py # サンプルコード open("test.txt").readlines() $ python sample.py # エラーが出ない
そもそもBOMとは何だったのか
BOMの除去方法が分かったは良いが、そもそもなぜこんなものが付いていることがあるのだろうか。BOMとはバイトオーダーマーク (Byte Order Mark) 、バイトオーダーを示すための記号である。 バイトオーダーとは「複数バイトからなるデータがあるとき、メモリに桁の大きいほうのバイトから並べるか、小さいほうから並べるか」 というお作法のことである。大きいほうのバイトから並べることをビッグエンディアン、小さいほうから並べる ことをリトルエンディアンと呼ぶ。 うっかりどこかでエンディアン取り違えが発生すると、先頭の"feff"が"fffe" (注:16進数なので2文字で1バイトになっている) になるので 気づける、というのがバイトオーダーマークのからくりである。
そもそもなんでこんなややこしいものを統一しないのか?とも思うが、歴史的経緯がありどうしようもない。 しかもCPUで主流のx86/x64はリトルエンディアンで動作するが、ネットワーク関係ではTCP/IPなどビッグエンディアンが定められている プロトコルが多くあり、データの受け渡しでは注意が必要なポイントである。
また一部のテキストエディタでは
- BOMあり:utf-8
- BOMなし:utf-8N (nobomのN)