複数 csv ⇒ まとめた変換後 csv 、的なことに使う itertools @ python

見出しに「csv」を含んでるだけで「XML さえあれば世界は平和なんだぜっ」とか「いやいや XML をデータに使うのはバカだって、json 使えや」で思考が中断している人々には黙殺されると思うんだけど、まぁそれはそれでいい。

じつは一連の MeCab 関係から出てきたニーズなのだが、利用条件が良くわからないある情報源を利用して辞書を作るネタなので、その「私的」本題は公開出来るものになるかどうかはわからない。

「利用条件が良くわからないある情報源」は単なる静的な HTML テーブルでの公開になっていて、私はそこからまずはシンプルな csv として抽出した。そして、そうやって抽出した csv を入力にして、例によって MeCab 辞書のための csv に仕立て上げる、てわけである。

この一連の変換「作業」はほとんど終了し、かなり満足できる出来になっているのだが、最後の最後で「元の情報源のページング」が鬱陶しくなってきた。すなわち、その情報源の html ページの一ページは「人間が読むのに大変ではない」ことを基準に千切られていて、このファイル数が結構多い。つまり単純に「あ行」「か行」…としているのではなくて、量が多いものは「かさ~」「かな~」のように細かく千切ってあるということ。

私の「辞書用 csv 生成」は、この入力から「もとのシステム辞書にあるものは除外」しつつ「他の品詞との衝突があるものを区別」しつつ「推定数」で分類しつつ、とさらに細かく分類していくので、たとえ元の情報源のレコード数がかなりの量でも、もとの単位だと結果的に一単位あたりのレコード数が激減してしまう。ので、「人間が読むのに大変」かどうかがむしろ「ファイル数が多い」かどうかの方にシフトしてしまった。私はほとんど emacs 内で過ごすため、emacs の dired モードでちまちまひとつひとつファイルを開く手間がかなりバカにならない。だったら一ファイルあたりの量が増えてでもある程度マージしてしまいたい。

というわけで「複数 csv ⇒ …」が必要になった、という話なんだけれど、見出しの意味はといえば、つまりはこのような元の実装を傷付けたくないということである:

1 def _to_mecab_input(fn, fromsysdic):
2     reader = csv.reader(io.open(fn, encoding="utf-8"))
3     # 入力 csv に基いて、MeCab 辞書用 csv を生成したい処理
4     for line in reader:  # csv.reader が行を列挙してくれることを期待している
5         # ...
6     with io.open("入力csvに基いた名前.csv", "wb") as fo:
7         fo.write(result.encode("utf-8"))

考えることは二つで、「reader が複数ファイルをシームレスにまたぐかのような振る舞い」と「入力csvに基いた名前」。

itertools で出来るだろうと考え、一応一発で期待通りに動いた:

 1 import csv
 2 from glob import glob
 3 import itertools
 4 
 5 # ...
 6 
 7 def _to_mecab_input(fn, reader, fromsysdic):  # reader も渡してしまう
 8     # 入力 csv に基いて、MeCab 辞書用 csv を生成したい処理
 9     for line in reader:  # csv.reader が行を列挙してくれることを期待している
10         # ...
11     with io.open("入力csvに基いた名前.csv", "wb") as fo:
12         fo.write(result.encode("utf-8"))
13 
14 if __name__ == '__main__':
15     # ...
16     #
17 
18     # 全体制御部分
19 
20     #   itertools.groupby で入力 csv の「名前の一致」でグルーピング
21     #   しつつ、itertools.chain.from_iterable で reader のイタレーション
22     #   を「チェイン」する。
23 
24     def _grp(it):
25         bn = os.path.splitext(os.path.basename(it))[0]
26         #return "a" if re.match(r"[aiueo]\d?", bn) else bn[0]
27         return re.sub(r"\d", "", bn)
28 
29     origs = sorted(glob("_orig/*.csv"))
30 
31     for fg in itertools.groupby(origs, _grp):
32         _reader = itertools.chain.from_iterable(
33             (csv.reader(io.open(fn, encoding="utf-8")) for fn in fg[1]))
34         _to_mecab_input(fg[0], _reader, fromsysdic)

groupby のネタも from_iterable のネタも前に単品で紹介したことはあったけれど、こういう使い方もあるよてことで。(ちなみにジェレネータの多用によって、おそらく見かけのようには一気にメモリを使うことはない、はず。そういう風に書いた。)