pythonでテキストファイルの分割をしたい


python3.x系です。

以下のようなテキストファイル（original.txt）があります。タブ区切りです。

### original.txt

[code]

    aaa data1_1 data1_2 data1_3
    aaa data2_1 data2_2 data2_3
    bbb data3_1 data3_2 data3_3
    ccc data4_1 data4_2 data4_3
    
[/code]

これを以下の３つのテキストファイルに分割したいのです。

### div001.txt

[code]

    aaa data1_1 data1_2 data1_3
    aaa data2_1 data2_2 data2_3
    
[/code]

### div002.txt

[code]

    bbb data3_1 data3_2 data3_3
    
[/code]

### div003.txt

[code]

    ccc data4_1 data4_2 data4_3
    
[/code]

・１列目のデータをキーワードにしてファイルを分割したい。  
・１列目のデータ（＝分割された後にできるファイル数）はこの例だと３つだが、何個であっても対応できるようにしたい

そこで、以下の部分までコードを考えました。

[code]

    import re
    
    with open("original.txt", "r") as fh_input:
        query_key = "" #キーワード
        list = [] #入力データの配列
        saved_key = "tekitou" #初期キーワード
        filenumber = 1 #ファイル番号用
        filename = "" #ファイル名用
    
        for line in fh_input:
            line_m = re.sub('[\r\n]+$', '', line)#改行コードの除去
            list = line_m.split('\t')#タブ区切りでリスト化
            query_key = list[0]
    
            if saved_key != query_key:
                filenumber_padded = '{0:03d}'.format(filenumber)
                filename = "div" + filenumber_padded + ".txt"
                filenumber += 1
    
                「何かのファイルハンドル」 = open(filename, "w")
                #ここに「filename」への書き込み処理がはいる
    
            saved_key = query_key
    
[/code]

とここまで書いて、ファイルハンドルを動的に（＝キーワードの種類数に合わせて自動的に）生成する方法を思いつかず、行き詰まりました。ファイルハンドルが動的に複数生成できれば、複数のファイルを開いて書き込むことができます。perlだと「ファイルハンドルとして未定義のスカラ変数が与えられたとき、ファイルハンドルを自動的に生成し変数に設定する」ということで、これを用いて動的に生成できるようです。しかし、pythonはどうも違うらしいのです。

そこで、ここから先どうしたらよいかお知恵を拝借したいのです。また、以上の方法だと、仮にファイルハンドルを動的に生成できたとしても、ファイルをいくつも同時に開きっぱなしにすることになる・・・かな。すると、original.txtが大きい場合、メモリに負担になりそうな気がします。この点もあわせてお知恵をお借りできればと思います。

よろしくおねがいします。




ジェネレータを使ってみました(エラーチェックはしていません)。

[code]

    import sys
    
    def outputs():
      n = 1
      while True:
        yield open('div{0:03d}.txt'.format(n), 'a') 
        n += 1
    
    if __name__ == '__main__':
      out = outputs()
      files = {}
      with open(sys.argv[1], 'r') as f:
        for l in f:
          key = l.split('\t')[0]
          if key not in files:
             files[key] = next(out)
             files[key].truncate(0)
          files[key].write(l)
      [f.close() for f in files.values()]
    
[/code]

>
> 以上の方法だと、仮にファイルハンドルを動的に生成できたとしても、ファイルをいくつも同時に開きっぱなしにすることになる・・・かな。すると、original.txtが大きい場合、メモリに負担になりそうな気がします。

メモリ以外にもプロセスが同時にオープンできる file descriptor 数には上限値がありますので、対策が必要になります(以下は
Ubuntu/Linux の場合)。

[code]

    $ lsb_release -d
    Description:    Ubuntu 18.04.1 LTS
    $ uname -sr
    Linux 4.15.0-33-generic
    $ ulimit -n
    1024
    $ ulimit -Hn
    1048576
    
[/code]

letrec
さんの言われる「開いて閉じて」を繰り返すのも一手ですが、一行毎では少し効率がよろしくないと思いますので、一定量をバッファリングして定期的にファイルへ出力するなどすると良いかもしれません。




ジェネレータを使ってみました(エラーチェックはしていません)。

[code]

    import sys
    
    def outputs():
      n = 1
      while True:
        yield open('div{0:03d}.txt'.format(n), 'a') 
        n += 1
    
    if __name__ == '__main__':
      out = outputs()
      files = {}
      with open(sys.argv[1], 'r') as f:
        for l in f:
          key = l.split('\t')[0]
          if key not in files:
             files[key] = next(out)
             files[key].truncate(0)
          files[key].write(l)
      [f.close() for f in files.values()]
    
[/code]

>
> 以上の方法だと、仮にファイルハンドルを動的に生成できたとしても、ファイルをいくつも同時に開きっぱなしにすることになる・・・かな。すると、original.txtが大きい場合、メモリに負担になりそうな気がします。

メモリ以外にもプロセスが同時にオープンできる file descriptor 数には上限値がありますので、対策が必要になります(以下は
Ubuntu/Linux の場合)。

[code]

    $ lsb_release -d
    Description:    Ubuntu 18.04.1 LTS
    $ uname -sr
    Linux 4.15.0-33-generic
    $ ulimit -n
    1024
    $ ulimit -Hn
    1048576
    
[/code]

letrec
さんの言われる「開いて閉じて」を繰り返すのも一手ですが、一行毎では少し効率がよろしくないと思いますので、一定量をバッファリングして定期的にファイルへ出力するなどすると良いかもしれません。

