У меня есть пользователи, загружающие CSV-файлы для загрузки. В Python 2 я смог открыть файл в двоичном формате, передать его в unicodecsv.DictReader
, и если в некоторых строках возникла проблема с кодировкой, например, недопустимый символ Unicode, потому что клиент использовал CP1251 или что-то еще, я мог записать эти строки ивернуть точно, какие строки имели проблему.
С py3.7 не похоже, что я могу это сделать - модуль csv
требует, чтобы файл был декодирован, и если я вместо этого передам егогенератор, как (line.decode('utf8') for line in my_binary_file)
, я не могу заставить его генерировать исключение только для плохих строк и продолжать идти после. Я попытался использовать unicodecsv
, хотя он не видел коммит более четырех лет и технически не поддерживает py> 3.5, и, похоже, он тоже не работает - итератор просто останавливается после неверной строки.
Я вижу два пути решения этой проблемы, ни один из которых не является привлекательным:
1) предварительно декодируйте файл построчно и находите плохие строки, что расточительно, или
2) пишите мой собственныйCSV-парсер, который позволяет пропускать плохие строки, что похоже на возникновение проблемы.
Могу ли я сделать это по-другому?
Для справки, вот пример кода, который работал в py2:
def unicode_safe_iterator(reader):
while True:
try:
yield True, next(reader)
except UnicodeDecodeError as exc:
yield False, 'UnicodeDecodeError: %s' % str(exc)
# uncomment for py3:
# except StopIteration:
# return
def get_data_iter_from_csv(csv_file, ...):
reader = unicodecsv.DictReader(csv_file)
error_messages = []
line_num = 1
for valid, row in unicode_safe_iterator(reader):
line_num += 1
if not valid:
error_messages.append(dict(line_number=line_num, error=row))
else:
row_data = validate_row_data(row) # check for errors other than encoding, etc.
if not error_messages:
# stop yielding in case of errors, but keep iterating to find all errors.
yield row_data
if error_messages:
raise ValidationError(Errors.CSV_FILE_ERRORS, error_items=error_messages)
data_iter = get_data_iter_from_csv(open(path_to_csv, 'rb'), ...)