Python - читать текстовый файл со странным форматом utf-16

Я пытаюсь прочитать текстовый файл в Python, но он, кажется, использует очень странную кодировку. Я стараюсь как обычно:

file = open('data.txt','r')

lines = file.readlines()

for line in lines[0:1]:
    print line,
    print line.split()

Выход:

0.0200197   1.97691e-005

['0\x00.\x000\x002\x000\x000\x001\x009\x007\x00', '\x001\x00.\x009\x007\x006\x009\x001\x00e\x00-\x000\x000\x005\x00']

Печать линии работает нормально, но после того, как я попытаюсь разбить линию, чтобы преобразовать ее в число с плавающей точкой, она выглядит сумасшедшей. Конечно, когда я пытаюсь преобразовать эти строки в числа с плавающей точкой, это приводит к ошибке. Любая идея о том, как я могу преобразовать их обратно в числа?

Я поместил образец файла данных здесь, если вы хотите попробовать загрузить его: https://dl.dropboxusercontent.com/u/3816350/Posts/data.txt

Я хотел бы просто использовать numpy.loadtxt или numpy.genfromtxt, но они также не хотят иметь дело с этим сумасшедшим файлом.

4 ответа

Решение

Готов поспорить, что это файл UTF-16-LE, и вы читаете его как любую кодировку по умолчанию.

В UTF-16 каждый символ занимает два байта.* Если все ваши символы являются ASCII, это означает, что кодировка UTF-16 выглядит как кодировка ASCII с дополнительным символом \x00 после каждого символа.

Чтобы это исправить, просто декодируйте данные:

print line.decode('utf-16-le').split()

Или сделайте то же самое на уровне файлов с модулем io или codecs:

file = io.open('data.txt','r', encoding='utf-16-le')

* Это немного упрощение: каждый символ BMP занимает два байта; каждый не-BMP символ превращается в суррогатную пару, причем каждый из двух суррогатов занимает два байта. Но вы, вероятно, не заботились об этих деталях.

Похоже, UTF-16 для меня.

>>> test_utf16 = '0\x00.\x000\x002\x000\x000\x001\x009\x007\x00'
>>> test_utf16.decode('utf-16')
u'0.0200197'

Вы можете работать непосредственно со строками Unicode:

>>> float(test_utf16)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: null byte in argument for float()
>>> float(test_utf16.decode('utf-16'))
0.020019700000000001

Или закодируйте их чем-то другим, если вы предпочитаете:

>>> float(test_utf16.decode('utf-16').encode('ascii'))
0.020019700000000001

Обратите внимание, что вам нужно сделать это как можно раньше при обработке. Как отметил ваш комментарий, split будет некорректно вести себя в кодированной форме utf-16. UTF-16 представление символа пространства ' ' является ' \x00', так split удаляет пробел, но оставляет нулевой байт.

2.6 и позже io библиотека может справиться с этим для вас, как и старые codecs библиотека. io лучше обрабатывает переводы строк, поэтому желательно, если доступно.

Это на самом деле просто предложение @abarnert, но я хотел опубликовать его как ответ, так как это самое простое решение, которое я в итоге использовал:

    file = io.open(filename,'r',encoding='utf-16-le')
    data = np.loadtxt(file,skiprows=8)

Это демонстрирует, как вы можете создать объект файла, используя io.open, используя любую сумасшедшую кодировку, которая есть в вашем файле, и затем передать этот объект файла в np.loadtxt (или np.genfromtxt) для быстрой и легкой загрузки.

Этот кусок кода сделает все необходимое

file_handle=open(file_name,'rb')
file_first_line=file_handle.readline()
file_handle.close()
print file_first_line
if '\x00' in file_first_line:
    file_first_line=file_first_line.replace('\x00','')
    print file_first_line

Когда вы пытаетесь использовать file_first_line.split() перед заменой, вывод будет содержать "\x00", я просто попытался заменить "\x00" пустым, и это сработало.

Другие вопросы по тегам