Док говорит:
'заменить':
Заменить подходящим маркером замены; 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