f

2015-01-18

Auto-detecting Japanese file enconding for Python3

Python3の標準機能だけでテキストファイルの日本語文字コードを自動判別する方法を記す。結論としては,fortryにより考えられるパターンを総当りでopenして試すことがベストだといえる。

Introduction

Pythonで日本語を使っていると文字コード(エンコード)関係のエラーに悩まされる。具体的にはファイルの入出力時や演算時の以下のような2種類のエラーにでくわす。

UnicodeDecodeError
UnicodeEncodeError

これはPythonが想定している文字コードと実際のデータの文字コードが違うために起こる。Python2系では文字のデータ型として,以下の2種類を持っている。

  • str型
  • unicode型

そのため,これらのデータ型が混在するとエラーとなる。Python3系では内部の文字のデータ型が実質的にunicodeであるstr型で統一されたためこの問題に対処しやすくなった。その代わりに,以下2点を明示的に指定する必要がある。

  • テキストファイル入力時のエンコード
  • テキストファイル出力時のエンコード

具体的には以下のように,open関数でencodingの値を指定して行う。

fr = open("./input.dat", "U", encoding="utf-8") # 入力
fw = open("./output.dat", "w", encoding="utf-8", newline="\n") # 出力

なお,入力時の改行コードに関しては,ファイルモードに"U"を指定することで,LF,CR,CR+LFをLFとして認識してくれる。出力時の改行コードについてもnewline="\n"を指定することで,LFとして出力できる。そのため,Python3での入出力時の改行コードの取扱は問題にならない。

文字コードについては,ファイル入出力時にだけエンコードを指定するため,問題の発生箇所を限定できわかりやすくなった。しかし,この入出力時にエンコードを明示的に指定する方法には欠点がある。それは,ファイル入力時にファイルの文字コードが不明である場合に,標準のPythonで自動的に文字コードを判定する方法が確立されていない点だ。

例えば,chardetのような外部ライブラリを使えばおそらく簡単に解決できるだろう。しかし,ファイルを開くときのエンコードの判定だけのために外部ライブラリを使うのは大げさである。そして以下のような欠点がある。

  • ネットがつながらない環境では,外部ライブラリをインストールできないため動作しない。
  • パッケージングする際にファイルサイズの肥大化につながる。

そのため,できる限りPython3の標準機能だけでファイルの文字コードを自動判別する方法が必要だ。

Method

調査の結果,Python3の標準機能だけで文字コードを自動判別するには以下の手順が簡単かつ依存性が少なくてベストだと判断した。

  • forとtryを駆使して,
  • 考えられる文字コードを総当りで,
  • 実際にファイルを開いて,
  • UnicodeDecodeErrorが出ないか確認

具体的にはに示すgetEncode関数を定義して,引数に開こうとするファイルパスを与えて,エンコードを取得する。考えられる日本語エンコードで実際にファイルを開いてエラーが出ないかどうか確認し,エラーが出れば次のエンコードを試し,エラーがなければそのエンコードを返している。

文字コードの自動判定関数
#!/usr/bin/env python3
# (File name: getenc.py)
# Lincense: CC0

def getEncode(filepath): encs = "iso-2022-jp euc-jp sjis utf-8".split() for enc in encs: with open(filepath, encoding=enc) as fr: try: fr = fr.read() except UnicodeDecodeError: continue return enc

FR = "./data.csv"
with open(FR, "U", encoding=getEncode(FR)) as fr:
fr = fr.read()

これにより日本語のテキストファイルであれば文字コードを自動的に判別してファイルを開くことができる。

Order of trying to encode

のコードにおいてポイントは以下のエンコードリストの順番だ。

    encs = "iso-2022-jp euc-jp sjis utf-8".split()

文字コードの判定を試すエンコードの順番が重要だ。

ISO-2022-JPとEUC-JPが曲者で,以下のようにSJISやUTF-8で誤判定(誤認識)されてしまう。

  • ISO-2022-JP:EUC-JP,UTF-8,SJISで誤判定。
  • EUC-JP:SJISで誤判定。

SJISとUTF-8はこの4種類では一意に判定される。つまり,ISO-2002-JP▷EUC-JP▷SJISの順番にエンコードを試していけばいい。この順番にしたがうと以下の3通りの組み合わせとなる。

  1. ISO-2022-JP▷EUC-JP▷SJIS▷UTF-8
  2. ISO-2022-JP▷EUC-JP▷UTF-8▷SJIS
  3. ISO-2022-JP▷UTF-8▷EUC-JP▷SJIS

判定のヒット率を考えると利用状況から3を使用したほうがよいかもしれない。しかし,後で見返すときに順番に意味があることをわかりやすくするために,日本語エンコード▷UTF-8の順番でやったほうがよいだろう。

このエンコードの順番は一般的にいえることのようで,例えばVimでエンコードのテキストファイルの自動判別を行うfencsの設定もこの順番にしたがわないと誤認識される。

set fileencodings=iso-2022-jp,euc-jp,sjis,cp932,utf-8

日本語エンコードの自動判別方法についてRubyでの状況も簡単に調べたが,標準ライブラリだけでやろうと思うと,に示したとおり総当りでやるしかなさそうだ。したがって,この総当りで文字コードを自動判別する方法は汎用性が高いといえる。

Appendix

今回は日本語の文字コードだけを対象としたが,全エンコードについて試してどんな言語のテキストファイルが来ても正しく判別できるようにしたいと思う人もいるだろう。

Python3が認識できるエンコードのリストは以下で示されている。

http://docs.python.jp/3/library/codecs.html#standard-encodings

利用可能なエンコードリストは/usr/lib/python3.4/encodings/aliases.pyに格納されており,以下のように標準ライブラリであるencodingモジュールのaliases属性でアクセス可能だ。

import encodings
encodings.aliases.aliases # エンコードとその名前の辞書が戻る

そのため,encs = encoding.aliases.aliases.keys()とすれば全エンコードを試せる。しかし,この方法はうまくいかない。なぜなら,encoding.aliases.aliasesには文字コード以外のエンコードも入るためだ。例えば,zipのエンコードなどバイナリファイルのエンコードも格納されており,途中でエラーが出る。また,辞書であるため誤った順番でエンコードが試され誤認識される。

したがって,全エンコードを試す場合は標準ライブラリの限定を諦めて,別の外部ライブラリに頼るべきだろう。

Reference

Python3での文字コードの自動判別にあたって参考にしたページを列挙する。数も多く玉石混交で整理するのが面倒なのでとりあえず他の人が参考にできるように掲載だけしておく。

エンコードとデコード.py - 3948番地 http://moon.my.coocan.jp/3948/wiki.cgi?page=%A5%A8%A5%F3%A5%B3%A1%BC%A5%C9%A4%C8%A5%C7%A5%B3%A1%BC%A5%C9.py

コンソール上でのみ使用されるプログラムの場合、入力文字の文字コードを推測する方法として、
import sys
decoded_string1 = unicode(input_string1, sys.getfilesystemencoding())
decoded_string2 = unicode(input_string2, sys.stdin.encoding)
というのがある。後者はコンソールから入力された文字の文字コードとしてはかなり信憑性がある。

Pythonにおける日本語のエンコーディングの検出について - 試験運用中なLinux備忘録 http://d.hatena.ne.jp/kakurasan/20100330/p1

[python3]文字コードの判定 | 「きまぐれほげほげひろば」のTOPICS http://chidipy.jpn.com/topics/?p=302

Python - テキストファイルのエンコーディングを自動判定して処理する - Qiita http://qiita.com/zarchis/items/3258562ebc9570fa05a3

pythonで丁寧な文字コード変換 - シコウサクゴ() http://d.hatena.ne.jp/yosshi71jp/20090915/1253034464

標準入出力、は
404 Blog Not Found:備忘録 - #python3 で sys.std(in|out|err) の encoding を強制する http://blog.livedoor.jp/dankogai/archives/51816624.html

  • Pythonであるディレクトリ以下のファイル全てに対して文字コードが何であるかチェックして出力 - Qiita http://qiita.com/selious/items/aa647128f54afe6a2063
    • このやり方は2系。

Python でエンコーディングを判定する | 傀儡師の館.Python - 楽天ブログ http://plaza.rakuten.co.jp/kugutsushi/diary/200805250000/

[mixi]文字コード判別 - Python | mixiコミュニティ http://mixi.jp/view_bbs.pl?comm_id=6869&id=68843693

技術志向 |Python 文字コードの判定と変換 http://speirs.blog17.fc2.com/blog-entry-4.html

[python3]文字コードの判定 | 「きまぐれほげほげひろば」のTOPICS http://chidipy.jpn.com/topics/?p=302

0 件のコメント:

コメントを投稿