Уже есть много хороших ответов, но недавно я столкнулся с подобной проблемой, и нужного мне решения здесь нет в списке, поэтому я решил, что могу дополнить эту тему.
80% времени, мне нужно читать файлы построчно. Затем, как предлагается в этом ответе , вы хотите использовать сам объект файла в качестве ленивого генератора:
with open('big.csv') as f:
for line in f:
process(line)
Однако недавно я столкнулся с очень большой (почти) однострочной CSV, где разделитель строк был фактически не '\n'
, а '|'
.
- Чтение построчно не было вариантом, но мне все еще нужно было обрабатывать его построчно.
- Преобразование
'|'
в '\n'
перед обработкой также не могло быть и речи, поскольку некоторые поля этого csv содержали '\n'
(свободный ввод текста пользователем).
- Использование библиотеки csv также было исключено, потому что тот факт, что, по крайней мере в ранних версиях библиотеки, жестко запрограммирован для чтения строки ввода строкой .
Я придумал следующий фрагмент:
def rows(f, chunksize=1024, sep='|'):
"""
Read a file where the row separator is '|' lazily.
Usage:
>>> with open('big.csv') as f:
>>> for r in rows(f):
>>> process(row)
"""
incomplete_row = None
while True:
chunk = f.read(chunksize)
if not chunk: # End of file
if incomplete_row is not None:
yield incomplete_row
break
# Split the chunk as long as possible
while True:
i = chunk.find(sep)
if i == -1:
break
# If there is an incomplete row waiting to be yielded,
# prepend it and set it back to None
if incomplete_row is not None:
yield incomplete_row + chunk[:i]
incomplete_row = None
else:
yield chunk[:i]
chunk = chunk[i+1:]
# If the chunk contained no separator, it needs to be appended to
# the current incomplete row.
if incomplete_row is not None:
incomplete_row += chunk
else:
incomplete_row = chunk
Я успешно проверил его на больших файлах и с разными размерами фрагментов (я даже пробовал размер фрагмента в 1 байт, просто чтобы убедиться, что алгоритм не зависит от размера).