2017-02-15 3 views
2

У меня есть следующая дилемма. Я разбираю огромные файлы CSV, которые теоретически могут содержать недопустимые записи, с python. Чтобы быстро исправить проблему, я хотел бы видеть номера строк в сообщениях об ошибках. Однако, поскольку я разбираю много файлов и ошибок, очень редко, я не хочу, чтобы моя обработка ошибок добавила накладные расходы к основному конвейеру. Вот почему я не хотел бы использовать enumerate или аналогичный подход.Значимые сообщения об ошибках IO без накладных расходов в Python

В двух словах, я ищу get_line_number функции работать так:

with open('file.csv', 'r') as f: 
    for line in f: 
     try: 
      process(line) 
     except: 
      line_no = get_line_number(f) 
      raise RuntimeError('Error while processing the line ' + line_no) 

Однако, это, кажется сложным, так как f.tell()will not work в этом цикле.

EDIT:

Похоже, что накладные расходы являются весьма значительными. В моем реальном случае (что является болезненным, поскольку файлы представляют собой списки довольно коротких записей: одиночные float, пары int-float или пары string-int, а file.csv - около 800 МБ и имеет около 80M строк), это около 2,5 секунд на файл, считанный для enumerate. По некоторым причинам fileinput является чрезвычайно медленно.

import timeit 
s = """ 
with open('file.csv', 'r') as f: 
    for line in f: 
     pass 
""" 
print(timeit.repeat(s, number = 10, repeat = 3)) 
s = """ 
with open('file.csv', 'r') as f: 
    for idx, line in enumerate(f): 
     pass 
""" 
print(timeit.repeat(s, number = 10, repeat = 3)) 
s = """ 
count = 0 
with open('file.csv', 'r') as f: 
    for line in f: 
     count += 1 
""" 
print(timeit.repeat(s, number = 10, repeat = 3)) 
setup = """ 
import fileinput 
""" 
s = """ 
for line in fileinput.input('file.csv'): 
    pass 
""" 
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3)) 

выходы

[45.790788270998746, 44.88589363079518, 44.93949336092919] 
[70.25306860171258, 70.28569177398458, 70.2074502906762] 
[75.43606997421011, 74.39759518811479, 75.02027251804247] 
[325.1898657102138, 321.0400970801711, 326.23809849238023] 

EDIT 2:

Приблизиться к реальной ситуации. Предложение try-except выходит за пределы цикла, чтобы уменьшить накладные расходы.

import timeit 
setup = """ 
def process(line): 
    if float(line) < 0.5: 
     outliers += 1 
""" 
s = """ 
outliers = 0 
with open('file.csv', 'r') as f: 
    for line in f: 
     process(line) 
""" 
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3)) 
s = """ 
outliers = 0 
with open('file.csv', 'r') as f: 
    try: 
     for idx, line in enumerate(f): 
      process(line) 
    except ValueError: 
     raise RuntimeError('Invalid value in line' + (idx + 1)) from None 
""" 
print(timeit.repeat(s, setup = setup, number = 10, repeat = 3)) 

выходы

[244.9097429071553, 242.84596176538616, 242.74369075801224 
[293.32093235617504, 274.17732743313536, 274.00854821596295] 

Итак, в моем случае, погоны из enumerate составляет около 10%.

+2

Итак, я должен спросить, проблема в том, что ваш пример, который будет работать, работает слишком медленно или что вы думаете, что он может работать слишком медленно? Какое влияние это оказывает на perf? Вы измеряли разницу в файле, который, как вы знаете, не имеет ошибок? –

+0

Ничего себе, не ожидал бы, что 2х замедляется. Какое влияние оказывает на обработку вашего 'process (line)' call в 'try/catch'? –

+0

И я тоже, но подставляя 'pass' для того, что бы действительно делалось с данными, не является самым справедливым из сравнений. Кроме того, всего десять байтов на строку файла csv довольно необычны. – nigel222

ответ

1

Если вы уверены, что добавление информации об отладке слишком много накладных расходов (я не хочу спорить по этой теме), вы можете реализовать две версии этой функции. Высокая производительность одна и одна с тщательной проверкой и подробной отладкой. Основная идея такова:

try: 
    func_quick(args) 
except Exception: 
    func_verbose(args) 

Недостатком является то, что обработка начнется снова при возникновении ошибки. Но если вам нужно вручную исправить ошибку, штраф в несколько секунд впустую в таком случае не должен навредить. Также func_verbose() не должен останавливаться на первой ошибке и может проверять весь файл и перечислять все ошибки.

2

ли использовать enumerate

for line_ref, line in enumerate(f): 
    line_no = line_ref + 1 # enumerate starts at zero 

Это не добавляет каких-либо значительных накладных расходов. Работа, связанная с получением записей из файла, значительно превышает работу, связанную с поддержанием счетчика, а назначение кортежей в инструкции for - это просто привязка имени, а не дополнительная копия данных, на которые ссылаются line

Обновление замены:

Ошибка при создании тестового файла. В настоящее время в значительной степени подтвердили, что первый тайм-тест добавлен в вопрос.

Лично я считаю, что 10% -ые накладные расходы на наихудший (ish) -case файл с 10-байтными записями вполне приемлемы, учитывая, что альтернатива не знает, какая из 80 миллионов записей была ошибочной.

+0

OP сказал, что он не хочет использовать' enumerate'. Если вы собираетесь публиковать ответ, используя что-то, что он сказал, что он не хочет использовать, вы могли бы хотя бы объяснить *, почему он захочет его использовать. –

+0

Только что заметил. Я обновил свой ответ. – nigel222

+0

Используйте 'enumerate (f, 1)', чтобы начать отсчет с 1. – VPfB

0

Стандартная библиотека fileinput модуль памяти эффективно обрабатывает большие файлы и предоставляет встроенный счетчик номеров строк. Он также автоматически выбирает несколько имен файлов для файлов, которые будут считываться из аргументов командной строки. Однако, похоже, нет (простой?) Способ использовать его с обработчиками контекста.

Что касается производительности, вам необходимо протестировать его по сравнению с другими подходами.

import fileinput 

for line in fileinput.input(): 
    try: 
     process(line) 
    except: 
     line_no = fileinput.filelineno() 
     raise RuntimeError('Error while processing the line ' + line_no) 

КСТАТИ Я рекомендовал бы ловить только соответствующие исключения, вероятно, пользовательские один, в противном случае вы будете маскировать непредвиденные исключения.

Смежные вопросы