Первая функция:
def filetoliststrip(file):
file_in = str(file)
lines = list(open(file_in, 'r'))
content = [x.strip() for x in lines]
return content
Здесь список необработанных строк создается только для удаления. Это потребует примерно вдвое больше памяти, чем необходимо, и, что не менее важно, несколько передач данных, которые не помещаются в кэш. Нам также не нужно постоянно делать str
вещей. Таким образом, мы можем немного упростить это:
def filetoliststrip(filename):
return [line.strip() for line in open(filename, 'r')]
Это все еще производит список. Если мы читаем данные только один раз, не сохраняя каждую строку, замените []
на ()
, чтобы превратить его в выражение генератора; в этом случае, поскольку строки фактически остаются нетронутыми в памяти до конца программы, мы сохраним место только для списка (который по-прежнему составляет не менее 30 МБ в вашем случае).
Тогда у нас есть основной цикл синтаксического анализа (я изменил отступ, как я думал, что это должно быть):
counter = 0
for line in fileinlist:
counter += 1
keyf = line[10:69]
print("Loading line " + str(counter) + " : " + str(line))
if keyf not in dict_in.keys():
dict_in[keyf] = []
dict_in[keyf].append(1)
dict_in[keyf].append(line)
else:
dict_in[keyf][0] += 1
dict_in[keyf].append(line)
Здесь есть несколько неоптимальных вещей.
Во-первых, счетчик может быть enumerate
(если у вас нет повторяемого элемента, есть range
или itertools.count
). Изменение этого поможет с ясностью и уменьшит риск ошибок.
for counter, line in enumerate(fileinlist, 1):
Во-вторых, более эффективно формировать строку за одну операцию, чем добавлять ее из битов:
print("Loading line {} : {}".format(counter, line))
В-третьих, нет необходимости извлекать ключи для проверки члена словаря. В Python 2 это создает новый список, который означает копирование всех ссылок, содержащихся в ключах, и становится медленнее с каждой итерацией. В Python 3 это по-прежнему означает создание объекта ключевого представления без необходимости. Просто используйте keyf not in dict_in
, если проверка необходима.
В-четвертых, проверка действительно не нужна. Поймать исключение, когда поиск не удается, почти так же быстро, как проверка if, и повторить поиск после проверки if почти наверняка медленнее. В этом отношении прекратите повторять поиск в общем:
try:
dictvalue = dict_in[keyf]
dictvalue[0] += 1
dictvalue.append(line)
except KeyError:
dict_in[keyf] = [1, line]
Это такой общий шаблон, однако, что у нас есть две реализации стандартной библиотеки: Counter
и defaultdict
. Мы могли бы использовать оба здесь, но Счетчик более практичен, когда вам нужен только счет.
from collections import defaultdict
def newentry():
return [0]
dict_in = defaultdict(newentry)
for counter, line in enumerate(fileinlist, 1):
keyf = line[10:69]
print("Loading line {} : {}".format(counter, line))
dictvalue = dict_in[keyf]
dictvalue[0] += 1
dictvalue.append(line)
Используя defaultdict
, давайте не будем беспокоиться о том, существуют записи или нет.
Теперь мы подошли к выходной фазе. Опять же, у нас есть ненужные поиски, поэтому давайте сократим их до одной итерации:
for key, value in dict_in.iteritems(): # just items() in Python 3
print("Processing key: " + key)
#print(value)
count, lines = value[0], value[1:]
if count < 2:
out_file.write(lines[0])
elif count == 2:
for line_in in lines:
out_file2.write(line_in + "\n")
elif count > 2:
for line_in in lines:
out_file3.write(line_in + "\n")
Это все еще имеет несколько неприятностей. Мы повторили написание кода, он строит другие строки (с тегами "\n"
), и у него есть целый кусок аналогичного кода для каждого случая. Фактически, повторение, вероятно, вызвало ошибку: в out_file
нет разделителя новой строки для единичных вхождений. Давайте рассмотрим, что действительно отличается:
for key, value in dict_in.iteritems(): # just items() in Python 3
print("Processing key: " + key)
#print(value)
count, lines = value[0], value[1:]
if count < 2:
key_outf = out_file
elif count == 2:
key_outf = out_file2
else: # elif count > 2: # Test not needed
key_outf = out_file3
key_outf.writelines(line_in + "\n" for line_in in lines)
Я оставил конкатенацию новой строки, потому что их сложнее объединить в отдельные вызовы. Строка недолговечна и служит для того, чтобы символ новой строки находился в одном и том же месте: на уровне ОС уменьшается вероятность того, что строка будет разбита параллельными записями.
Вы заметите, что здесь есть различия между Python 2 и 3. Скорее всего, ваш код не был таким уж медленным, если его запустить в Python 3. Существует модуль совместимости под названием six для написания кода, который легче выполнять в любом из них; это позволяет вам использовать, например, six.viewkeys
и six.iteritems
, чтобы избежать этой ошибки.