Cucco’s Compute Hack

コンピュータ関係の記事を書いていきます。

分割してCSVに書く、分割されたCSVを読む

1年前のプログラムの一部改良。
複数ファイルを連続して読むプログラム - Cucco’s Compute Hack

ディレクトリのパスと、ファイル名(拡張子ナシ、連番ナシ)を与えて、
決まった行数を書いたファイルを保存する、それらのファイルを開いて読む、というプログラム。

読む場合は以前から継続で、イテレータを利用できるようにした。
読むファイルをすべてリストで指定していたが、pathlibで一覧を取得している。

プログラム
import csv
from pathlib import Path
import os

from faker import Faker
import random


class split_csv_writer:
    '''
    渡された値をファイルに書き落としていく
    max_linesを超えたら、別のファイルに変更する。
    '''

    def __init__(self, dirctory_path, filename_wo_extension, header_data=None, max_lines=100000):
        self.max_lines = max_lines
        if header_data == None:
            self.max_lines = max_lines-1
        self.currnet_lines = self.max_lines+1  # 初回にOpenを実行してもらうため。

        self.currnet_file_post_index = 0
        self.filename = filename_wo_extension
        self.f = None
        self.dirctory_path = dirctory_path
        self.header_data = header_data

        self.delete_existing_files()

    def close(self):
        # デストラクタでも呼んでいる
        self.currnet_file_post_index = self.currnet_file_post_index+1
        if self.f != None:
            self.f.flush()  # 実際には、ファイルに書き込みしてくれないことがあったので追加。
            self.f.close()
        self.f = None

    def delete_existing_files(self):
        p = Path(self.dirctory_path)
        r = self.filename+"*.csv"
        for existing_file_path in p.glob(r):
            print("削除します ", existing_file_path)
            os.remove(existing_file_path)

    def __del__(self):
        self.close()

    def open(self):
        if self.f == None:
            pass
        else:
            self.close()

        real_filename = self.dirctory_path + self.filename + \
            "_{0:08d}.csv".format(self.currnet_file_post_index)

        os.makedirs(self.dirctory_path, exist_ok=True)

        self.f = open(real_filename, 'w', encoding='utf-8')
        self.csv_writer = csv.writer(
            self.f, delimiter=',', quotechar='"', lineterminator='\n')
        self.currnet_lines = 0
        print("新規作成 ", real_filename)

    def writerow(self, input_data):
        '''
        input_dataには、リストかタプルでデータを与える。
        '''
        if self.currnet_lines > self.max_lines:
            self.open()
            if self.header_data != None:
                self.writerow(self.header_data)

        self.csv_writer.writerow(input_data)
        self.currnet_lines = self.currnet_lines+1


class split_csv_reader:
    def __init__(self, dirctory_path, readfilename_wo_extention, skipHeader=False):
        self.filename = readfilename_wo_extention
        self.currentFileIndex = -1
        self.fp = None
        self.csv_reader = None
        self.skipHeader = skipHeader
        self.dirctory_path = dirctory_path
        self.set_readFileNames()

    def set_readFileNames(self):
        self.readFileNames = []
        p = Path(self.dirctory_path)
        r = self.filename+"*.csv"
        for existing_file_path in p.glob(r):
            self.readFileNames.append(existing_file_path)

        # ⃣print(self.readFileNames)

    def __iter__(self):
        # next()はselfが実装してるのでそのままselfを返す
        return self

    def __next__(self):

        if self.csv_reader is None:
            # 1個目のファイルを開く
            self.nextfile()

        try:
            value = next(self.csv_reader)
            return value
        except StopIteration:
            # 終端まで来たら、このStopIterationが投げられる。
            try:
                self.nextfile()
                value = next(self.csv_reader)
                return value
            except StopIteration:
                # 次のファイルがなかったので、例外を投げてイテレータを終わる。
                # ファイルがロックされている場合もここに来るので、問題になる可能性あり。
                raise (StopIteration)
        except:
            # ここには来ない。
            raise (StopIteration)

    def nextfile(self):
        self.currentFileIndex = self.currentFileIndex + 1
        if self.fp is not None:
            self.fp.close()
        if self.currentFileIndex > len(self.readFileNames)-1:
            # 次のファイルはないので例外を投げる
            raise(StopIteration)
        else:
            self.fp = open(
                self.readFileNames[self.currentFileIndex], mode='r', newline='\n')
            self.csv_reader = csv.reader(self.fp, delimiter=',', quotechar='"')

            if self.skipHeader is True:
                next(self.csv_reader)
                # ⃣print(value)

# 動作確認用プログラム
if __name__ == "__main__":
    dirname = r"C:/lib_split_test/"  # 最後はos.sepで終わるように指定する
    filename = r"lib_split_test"

    print("#書き込みのテスト")
    headerdata = ("#", "Name", "score")
    writer = split_csv_writer(
        dirname, filename, header_data=headerdata, max_lines=3)

    fake = Faker()
    for i in range(1, 10+1):  # 最後の値は実行されないrange(1,10+1)で、1~10
        data = [i, fake.name(), random.random()]
        writer.writerow(data)
        print(data)
    writer.close()

    print("#読み込みのテスト")

    reader = split_csv_reader(dirname, filename, skipHeader=True)

    for item in reader:
        print(item)
実行結果
(base) C:\dev\ > python C:\dev\lib_sprit_csv_reader_writer.py
#書き込みのテスト
削除します  C:\lib_split_test\lib_split_test_00000000.csv
削除します  C:\lib_split_test\lib_split_test_00000001.csv
削除します  C:\lib_split_test\lib_split_test_00000002.csv
削除します  C:\lib_split_test\lib_split_test_00000003.csv
新規作成  C:/lib_split_test/lib_split_test_00000000.csv
[1, 'Carol Martinez', 0.2993282973465532]
[2, 'Monica Skinner', 0.47771227744058886]
[3, 'Stephanie Zimmerman', 0.30639552562401595]
新規作成  C:/lib_split_test/lib_split_test_00000001.csv
[4, 'Terry Williams', 0.2800113471343493]
[5, 'Scott Grant', 0.8271274374443526]
[6, 'Jose Daniels', 0.4170927481515181]
新規作成  C:/lib_split_test/lib_split_test_00000002.csv
[7, 'Adam Austin', 0.2850528257749132]
[8, 'Krista Williams', 0.9258438865231986]
[9, 'Jared Adkins', 0.6205699038539508]
新規作成  C:/lib_split_test/lib_split_test_00000003.csv
[10, 'Ryan Morales', 0.9635161039757013]
#読み込みのテスト
['1', 'Carol Martinez', '0.2993282973465532']
['2', 'Monica Skinner', '0.47771227744058886']
['3', 'Stephanie Zimmerman', '0.30639552562401595']
['4', 'Terry Williams', '0.2800113471343493']
['5', 'Scott Grant', '0.8271274374443526']
['6', 'Jose Daniels', '0.4170927481515181']
['7', 'Adam Austin', '0.2850528257749132']
['8', 'Krista Williams', '0.9258438865231986']
['9', 'Jared Adkins', '0.6205699038539508']
['10', 'Ryan Morales', '0.9635161039757013']