Итерация строкового файла Python и странные символы

У меня есть огромный текстовый файл, сжатый gzip, который мне нужно читать построчно. Я иду со следующим:

for i, line in enumerate(codecs.getreader('utf-8')(gzip.open('file.gz'))):
  print i, line

В какой-то момент в конце файла вывод python отличается от файла. Это связано с тем, что строки разрываются из-за странных специальных символов, которые python считает новыми строками. Когда я открываю файл в «vim», они верны, но подозрительные символы имеют странный формат. Есть ли что-то, что я могу сделать, чтобы исправить это?

Я пробовал другие кодеки, включая utf-16, latin-1. Я также пробовал без кодека.

Я посмотрел файл, используя «od». Конечно, есть символы \n там, где их быть не должно. Но «неправильным» предшествует странный символ. Я думаю, что здесь есть некоторая кодировка с некоторыми символами, состоящими из 2 байтов, но конечный байт представляет собой \n, если его не рассматривать должным образом.

Согласно файлу od -h, оскорбительным символом является 1d1c.

Если я заменю:

gzip.open('file.gz')

С:

os.popen('zcat file.gz')

Работает нормально (и на самом деле, гораздо быстрее). Но я хотел бы знать, где я ошибаюсь.


person muckabout    schedule 29.04.2010    source источник
comment
Трудно диагностировать, не зная, как выглядит оскорбительная линия. Можете ли вы выяснить, какой это номер строки, а затем, возможно, опубликовать шестнадцатеричный дамп необработанной строки в файле, используя что-то вроде xxd (или с помощью vim вы можете использовать ctrl-a, чтобы найти шестнадцатеричный код символа)   -  person Daniel DiPaolo    schedule 29.04.2010
comment
Покажите нам вывод print repr(weird_special_characters). Когда вы открываете файл в vim, ЧТО правильно? Пожалуйста, будьте более точны, чем странно отформатированы.   -  person John Machin    schedule 29.04.2010


Ответы (2)


Попробуйте еще раз без кодека. Ниже воспроизводится ваша проблема при использовании кодека и отсутствие проблемы без него:

import gzip 
import os 
import codecs 

data = gzip.open("file.gz", "wb") 
data.write('foo\x1d\x1cbar\nbaz') 
data.close() 

print list(codecs.getreader('utf-8')(gzip.open('file.gz'))) 
print list(os.popen('zcat file.gz')) 
print list(gzip.open('file.gz')) 

Выходы:

[u'foo\x1d', u'\x1c', u'bar\n', u'baz']
['foo\x1d\x1cbar\n', 'baz']
['foo\x1d\x1cbar\n', 'baz']
person DS.    schedule 02.05.2010

Я спросил (в комментарии) """Покажите нам вывод из print repr(weird_special_characters). Когда вы открываете файл в vim, ЧТО правильно? Пожалуйста, будьте более точны, чем "странно отформатирован".""" Но ничего: - (

Какой файл вы просматриваете с помощью od? file.gz?? Если вы видите там что-то узнаваемое, это не файл gzip! Вы не видите новые строки, вы видите двоичные байты, содержащие 0x0A.

Если исходный файл был в кодировке utf-8, какой смысл было пробовать его с другими кодеками?

Означает ли «нормально работает с zcat», что вы получили распознаваемые данные без шага декодирования utf8 ??

Я предлагаю вам упростить свой код и делать это шаг за шагом... см., например, принятый ответ на этот вопрос. Попробуйте еще раз и, пожалуйста, покажите точный код, который вы запустили, и используйте repr() при описании результатов.

Обновление Похоже, DS догадался, что вы пытались объяснить о \x1c и \x1d.

Вот несколько заметок о том, ПОЧЕМУ это происходит:

В ASCII при разрыве строки учитываются только \r и \n:

>>> import pprint
>>> text = ''.join('A' + chr(i) for i in range(32)) + 'BBB'
>>> print repr(text)
'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\nA\x0bA\x0cA\rA\x0eA\x0fA\x10
A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x1dA\x1eA\x1fBBB'
>>> pprint.pprint(text.splitlines(True))
['A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\n', # line break
 'A\x0bA\x0cA\r', # line break
 'A\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x
1dA\x1eA\x1fBBB']
>>>

Однако в Unicode символы \x1D (РАЗДЕЛИТЕЛЬ ФАЙЛОВ), \x1E (РАЗДЕЛИТЕЛЬ ГРУПП) и \x1E (РАЗДЕЛИТЕЛЬ ЗАПИСЕЙ) также считаются окончанием строки:

>>> text = u''.join('A' + unichr(i) for i in range(32)) + u'BBB'
>>> print repr(text)
u'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\nA\x0bA\x0cA\rA\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1cA\x1dA\x1eA\x1fBBB'
>>> pprint.pprint(text.splitlines(True))
[u'A\x00A\x01A\x02A\x03A\x04A\x05A\x06A\x07A\x08A\tA\n', # line break
 u'A\x0bA\x0cA\r', # line break
 u'A\x0eA\x0fA\x10A\x11A\x12A\x13A\x14A\x15A\x16A\x17A\x18A\x19A\x1aA\x1bA\x1c', # line break
 u'A\x1d', # line break
 u'A\x1e', # line break
 u'A\x1fBBB']
>>>

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

person John Machin    schedule 30.04.2010
comment
Я смотрю несжатый файл (в частности, проблемную часть) с помощью od. Вот где появляется '\n'. Я считаю, что на самом деле это 2-байтовый символ, который, когда он представлен как 2 отдельных байта, имеет «\ n». К сожалению, я не знаю исходной кодировки, но по какой-то причине vim и zcat представляют ее правильно, то есть обнаруживают. - person muckabout; 01.05.2010
comment
Несжатый файл??? Ваш код не показывает, что вы создаете файл. Пожалуйста, покажите код, который вы на самом деле запустили, создав файл, который вы изучаете с помощью od. Пожалуйста, покажите шестнадцатеричный дамп, скажем, первых 100 байт файла. Пожалуйста, покажите шестнадцатеричный дамп из од, скажем, 100 байт, центрированный на оскорбительном `\n'. Пожалуйста, покажите все это, отредактировав свой вопрос. - person John Machin; 01.05.2010
comment
Сжатый файл весит 10 ГБ. Проблема не в первых 100 строках, а в строке, близкой к 1M. Я напечатал первую часть, которой, по-видимому, было достаточно, чтобы понять проблему. Мой код не распаковывает код, я сделал это вручную для диагностики. Мой код не создает файл, так что это тоже не имеет значения. Спасибо, что предложили помощь, но вы могли бы быть менее воинственным. - person muckabout; 03.05.2010