Python3 читает смешанные текстовые / двоичные данные построчно - PullRequest
0 голосов
/ 06 сентября 2018

Мне нужно проанализировать файл с текстовым заголовком UTF-16, за которым следуют двоичные данные. Чтобы прочитать двоичные данные, я открываю файл в режиме «rb», затем для чтения заголовка оборачиваю его в io.TextIOWrapper ().

Проблема заключается в том, что когда я выполняю метод .readline() объекта TextIOWrapper, оболочка слишком сильно читает вперед (даже если я запросил только одну строку), а затем сталкивается с ошибкой декодирования UTF-16 при обнаружении двоичная часть: A UnicodeDecodeError повышен.

Однако мне нужен правильный синтаксический анализ текстовых данных, и я не могу просто сначала выполнить двоичное чтение, а затем выполнить data.find (b "\ n \ 0"), потому что не гарантируется, что это действительно совпадает с четным смещением ( может быть на полпути между персонажами). Я бы не хотел разбирать UTF-16 самостоятельно.

Есть ли простой способ сказать TextIOWrapper не читать вперед?

1 Ответ

0 голосов
/ 06 сентября 2018

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

Для отдельной строки текста с использованием \n разделителей строк вам действительно не нужно использовать TextIOWrapper(). Двоичные файлы по-прежнему поддерживают построчное чтение, где file.readline() даст вам двоичные данные вплоть до следующего \n байта. Просто откройте файл как двоичный файл и прочитайте одну строку.

Допустимые данные UTF-16 будут всегда иметь четную длину. Но поскольку UTF-16 поставляется в двух вариантах: байты с прямым и прямым порядком байтов, вам нужно проверить, сколько данных было прочитано, чтобы увидеть, какой порядок байтов был использован, чтобы условно прочитать один байт, который должен быть частью эта первая строка данных. Если использовался UTF-16 little-endian, вы гарантированно прочитали нечетное число байтов, так как символы новой строки кодируются в 09 00, а не 00 90 и вызов .readline() оставит один байт 00 в файловом потоке. В этом случае просто прочитайте еще один байт и добавьте его в данные первой строки перед декодированием:

with open(filename, 'rb') as binfile:
    firstline = binfile.readline()
    if len(firstline) % 2:
        # little-endian UTF-16, add one more byte
        firstline += binfile.read(1)
    text = firstline.decode('utf-16')

    # read binary data from the file

Демонстрация с io.BytesIO(), где мы сначала записываем данные с прямым порядком байтов в UTF-16 (с помощью спецификации, указывающей порядок байтов для декодера), с текстом, за которым следуют две последовательности с низкими суррогатами, которые могут вызвать UTF- 16 Ошибка декодирования для обозначения «двоичных данных», после чего мы снова читаем текст и данные:

>>> import io, codecs
>>> from pprint import pprint
>>> binfile = io.BytesIO()
>>> utf16le_wrapper = io.TextIOWrapper(binfile, encoding='utf-16-le', write_through=True)
>>> utf16le_wrapper.write('\ufeff')  # write the UTF-16 BOM manually, as the -le and -be variants won't include this
1
>>> utf16le_wrapper.write('The quick brown ? jumps over the lazy ?\n')
40
>>> binfile.write(b'\xDF\xFF\xDF\xFF')  # binary data, guaranteed to not decode as UTF-16
4
>>> binfile.flush()  # flush and seek back to start to move to reading
>>> binfile.seek(0)
0
>>> firstline = binfile.readline()  # read that first line
>>> len(firstline) % 2              # confirm we read an odd number of bytes
1
>>> firstline += binfile.read(1)    # add the expected null byte
>>> pprint(firstline)               # peek at the UTF-16 data we read
(b'\xff\xfeT\x00h\x00e\x00 \x00q\x00u\x00i\x00c\x00k\x00 \x00b\x00r\x00o\x00'
 b'w\x00n\x00 \x00>\xd8\x8a\xdd \x00j\x00u\x00m\x00p\x00s\x00 \x00o\x00v\x00'
 b'e\x00r\x00 \x00t\x00h\x00e\x00 \x00l\x00a\x00z\x00y\x00 \x00=\xd8\x15\xdc'
 b'\n\x00')
>>> print(firstline.decode('utf-16'))  # bom included, so the decoder detects LE vs BE
The quick brown ? jumps over the lazy ?

>>> binfile.read()
b'\xdf\xff\xdf\xff'

Любые альтернативные реализации, которые все еще могут использовать TextIOWrapper(), требуют, чтобы между двоичным файлом и экземпляром TextIOWrapper() находилась промежуточная оболочка, чтобы TextIOWrapper() не читал слишком далеко, и это усложняло бы fast и требует, чтобы оболочка знала, какой кодек используется в любом случае. Для одной строки текста это не стоит затраченных усилий.

...