ошибки = суррогатный пейзаж против ошибок = заменить - PullRequest
0 голосов
/ 04 июня 2019

Я пытаюсь открыть такой файл:

with open("myfile.txt", encoding="utf-8") as f:

, но myfile.txt исходит от пользователей моего приложения.И в 90% случаев этот файл не соответствует UTF-8, что приводит к завершению работы приложения, поскольку оно не может правильно его прочитать.Ошибка выглядит так: 'utf-8' codec can't decode byte 0x9c

Я прогуглил об этом и нашел несколько ответов Stackoverflow, в которых говорится, чтобы открыть мой файл следующим образом:

with open("myfile.txt", encoding="utf-8", errors="surrogateescape") as f:

, но другие ответы сказали использовать:

with open("myfile.txt", encoding="utf-8", errors="replace") as f:

Так в чем же разница между errors="replace" и errors="surrogateescape" и какой из них будет исправлять байты, отличные от UTF-8, в файле?

Ответы [ 2 ]

2 голосов
/ 04 июня 2019

Док говорит:

'заменить': Заменить подходящим маркером замены; Python будет использовать официальный U + FFFD REPLACEMENT CHARACTER для встроенных кодеков при декодировании и «?» При кодировании. Реализовано в replace_errors ().
...
'surrogateescape': при декодировании замените байт отдельным суррогатным кодом в диапазоне от U + DC80 до U + DCFF. Этот код будет затем возвращен в тот же байт, когда при кодировании данных используется обработчик ошибок 'surrogateescape'. (Подробнее см. PEP 383.)

Это означает, что при replace любой нарушающий байт будет заменен тем же U+FFFD REPLACEMENT CHARACTER, тогда как при surrogateescape каждый байт будет заменен на другое значение. Например, '\xe9' будет заменен на '\udce9' и '\xe8' на '\udce8'.

Таким образом, при замене вы получаете действительные символы Юникода, но теряете исходное содержимое файла, в то время как с помощью surrogateescape вы можете знать исходные байты (и даже можете перестроить его точно с помощью .encode(errors='surrogateescape')), но ваша строка в Юникоде неверно, поскольку содержит необработанные суррогатные коды.

Короче говоря: если исходные ошибочные байты не имеют значения, и вы просто хотите избавиться от ошибки, replace - хороший выбор, и если вам нужно сохранить их для дальнейшей обработки, surrogateescape - это путь.


surrogateescape имеет очень приятную особенность, когда у вас есть файлы, содержащие в основном символы ascii и несколько (акцентированных) не ascii символов. И у вас также есть пользователи, которые иногда изменяют файл с помощью редактора, отличного от UTF8 (или не могут объявить кодировку UTF8). В этом случае вы получите файл, содержащий в основном данные utf8 и несколько байтов в другой кодировке, часто CP1252 для пользователей Windows на неанглийском западноевропейском языке (например, на французском, испанском и португальском). В этом случае можно построить таблицу перевода, которая отобразит суррогатные символы в их эквивалент в кодировке cp1252:

# first map all surrogates in the range 0xdc80-0xdcff to codes 0x80-0xff
tab0 = str.maketrans(''.join(range(0xdc80, 0xdd00)),
             ''.join(range(0x80, 0x100)))
# then decode all bytes in the range 0x80-0xff as cp1252, and map the undecoded ones
#  to latin1 (using previous transtable)
t = bytes(range(0x80, 0x100)).decode('cp1252', errors='surrogateescape').translate(tab0)
# finally use above string to build a transtable mapping surrogates in the range 0xdc80-0xdcff
#  to their cp1252 equivalent, or latin1 if byte has no value in cp1252 charset
tab = str.maketrans(''.join(chr(i) for i in range(0xdc80, 0xdd00)), t)

Затем вы можете декодировать файл, содержащий моджибаке utf8 и cp1252:

with open("myfile.txt", encoding="utf-8", errors="surrogateescape") as f:
    for line in f:                     # ok utf8 has been decoded here
        line = line.translate(tab)     # and cp1252 bytes are recovered here

Я успешно использовал этот метод несколько раз для восстановления CSV-файлов, которые были созданы как utf8 и были отредактированы с помощью Excel на машинах Windows.

Тот же метод может быть использован для других кодировок, полученных из ascii

0 голосов
/ 04 июня 2019

Моя проблема заключалась в том, что в файле были строки со смешанными типами кодирования.

Исправлено было удалить encoding="utf-8" и добавить errors="replace" .. Таким образом, строка open() была бы такой же в конце:

with open("myfile.txt", errors="replace") as f:

Если бы было возможно определить тип кодировки файла, я бы добавил его в качестве параметра encoding, но, к сожалению, обнаружить его невозможно.

...