Разбиение файла gz размером 3 ГБ на куски

У меня есть файл gz размером 3 ГБ, который я пытаюсь разбить на куски меньших файлов, которые не обязательно должны быть gz (я пытался создать файлы из 10000000 строк, это не является требованием, фрагменты могут быть любого размера), но мой метод требует времени на выполнение (около 12 минут). У меня есть требование не использовать какие-либо библиотеки, такие как Pandas, Spark и т. Д. Я могу использовать только чистый Python. Я пробовал профилировать свой код, и запись кажется самой медленной. Вот мой код:

import gzip
import os

class FileSplitter:

    def __init__(self):
        self.parse_args(sys.argv)

    @staticmethod
    def run():
        splitter = FileSplitter()
        #run to split the big file into smaller files
        splitter.split()
    

    def split(self):

        file_number = 1
        line_number = 0

        print("Splitting %s into multiple files with %s lines" % (os.path.join(self.working_dir, self.file_base_name), str(self.split_size)))

        out_file = self.get_new_file(file_number)
        a_file = gzip.open(self.in_file, 'rt')
        t = next(a_file)
        with a_file as f: 
            for line in f:
                out_file.write(line)
                line_number += 1
                if line_number == self.split_size:
                    out_file.close()
                    file_number += 1
                    line_number = 1
                    out_file = self.get_new_file(file_number)

        out_file.close()

        print("Created %s files." % (str(file_number)))

    def get_new_file(self,file_number):
        """return a new file object ready to write to"""
        new_file_name = "%s.%s" % (self.file_base_name, str(file_number))
        new_file_path = os.path.join(self.working_dir, 'pychunks', new_file_name)
        print ("creating file %s" % (new_file_path))
        return open(new_file_path, 'w')

    def parse_args(self,argv):
        """parse args and set up instance variables"""
        try:
            self.split_size = 10000000
            if len(argv) > 2:
                self.split_size = int(argv[2])
            self.file_name = argv[1]
            self.in_file = self.file_name
            self.working_dir = os.getcwd()
            self.file_base_name = os.path.basename(self.file_name)
        except:
            print(self.usage())
            sys.exit(1)

    def usage(self):
        return """
        Split a large file into many smaller files with set number of rows.

        Usage:

            $ python file_splitter.py <file_name> [row_count]

        row_count is optional (default is 1000)"""

if __name__ == "__main__":
    FileSplitter.run()

Чтобы запустить свой код, я вызываю файл .py, содержащий этот код, с именем моего файла gz, например: python myprogfile.py test.gz

Вы бы поступили иначе? Вы бы использовали потоки (я пробовал, но пока не смог, так как у меня нет опыта в этом)? Что бы вы изменили в этом коде?

Я не знаю, мой ноутбук слишком медленный или слишком слабый с точки зрения памяти и процессора …

1 ответ
1

Что бы вы изменили в этом коде?

Я бы упростил split функция. Не считая сами строки и файлы и открывая / закрывая выходные файлы в разных местах без with заявление. Просто всегда пытайтесь прочитать еще одну строку, и если это удастся, поместите ее и следующую до split_size - 1 строк в другой выходной файл.

    def split(self):
        print("Splitting %s into multiple files with %s lines" % (os.path.join(self.working_dir, self.file_base_name), str(self.split_size)))

        with gzip.open(self.in_file, 'rt') as fin:
            for file_number, line in enumerate(fin, start=1):
                with self.get_new_file(file_number) as fout:
                    fout.write(line)
                    fout.writelines(itertools.islice(fin, self.split_size - 1))

        print("Created %s files." % (str(file_number)))

Хотя я часто использую f когда я работаю только с одним файлом, мне не нравились очень разные имена f и out_file. В таких случаях я использую fin и fout (и я думаю, что это довольно часто), поэтому я тоже изменил это.

Я бы, наверное, тоже поменял внутреннее with к with open(...) as fout и имеют get_new_file вернуть только имя файла, а не открыть сам файл. Я бы предпочел оставить open в with заявление и иметь get_new_file быть функцией без таких побочных эффектов. Или если ваша единственная причина иметь get_new_file вообще было то, что у вас было два места для его использования, то теперь он вам больше не нужен, и вы можете просто создать имя файла внутри split функция.

Все еще не исправленные проблемы в вашем коде:

  • Пока вы исправили один line_number = 1 (от ваш оригинал) к line_number = 0, вы забыли другой. Это делает ваши куски на одну строку слишком короткими. Не было бы, если бы у вас было только одно место 🙂
  • Отсутствие импорта sys, вызывая NameError: name 'sys' is not defined.
  • Отсутствие создания папки pychunks. Без этого пользователь получит довольно запутанную трассировку и FileNotFoundError: [Errno 2] No such file or directory: '/home/runner/p/pychunks/main.py.gz.1'.
  • t = next(a_file) теряет первую строку, так как она никогда не используется. Может быть что ты хотите, но это не задокументировано и противоречит здравому смыслу и описанию, которое является там и отображается при запуске скрипта без аргументов. Возможно, если вы действительно этого хотите, сделайте это аргументом командной строки для пропуска первого числа строк со значением по умолчанию 0.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *