JP / EN

広告

"\ufeff"から始まるBOM付uftをPythonで読み込む

タグ:python

たまにPythonでファイルを読み込もうとしたときに 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, in 
        open("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)
の様な表記をしてある。これだと何もつかない一見して無害そうな utf-8がbom付きなので気を付けよう。


このエントリーをはてなブックマークに追加

https://wonderhorn.net/