У меня есть файл 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 ответ
Что бы вы изменили в этом коде?
Я бы упростил 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
.