Python 2.7.3 UTF-8 Кодировка необратимая
Я наткнулся на несколько очень неприятных строк при сканировании сети. В частности, страница рекламируется как UTF-7
и хотя это не совсем UTF-7
это не кажется проблемой. Меня не интересует точное представление текста, но мне просто нужно UTF-8
для последующего потребления.
Странность, с которой я сталкиваюсь, заключается в том, что я могу получить unicode
строка, которая не может быть первой UTF-8
закодирован и затем декодирован. Я вычеркнул строку изо всех сил, все еще показывая ошибку:
bytes = [43, 105, 100, 41, 46, 101, 95, 39, 43, 105, 100, 43]
string = ''.join(chr(c) for c in bytes)
# This particular string happens to be advertised as UTF-7, though it is
# a bit malformed. We'll ignore these errors when decoding it.
decoded = string.decode('utf-7', 'ignore')
# This decoded string, however, cannot be encoded into UTF-8 and back:
error = decoded.encode('utf-8').decode('utf-8')
Я попробовал это на нескольких системах: Python 2.7.1 и 2.6.7 на Mac 10.5.7, Python 2.7.2 и 2.6.8 на CentOS. К сожалению, на машинах нам нужно, чтобы это работало на сбое с Python 2.7.3 на Ubuntu 12.04. В сбойной системе я вижу:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xf7 in position 4: invalid start byte
Вот некоторые промежуточные значения, которые я вижу в рабочих и нерабочих системах:
# Working:
>>> repr(decoded)
'u".e_\'\\u89df"'
>>> repr(decoded.encode('utf-8'))
'".e_\'\\xe8\\xa7\\x9f"'
# Non-working:
>>> repr(decoded)
'u".e_\'\\U089d89df"'
>>> repr(decoded.encode('utf-8'))
'".e_\'\\xf7\\x98\\xa7\\x9f"'
Они отличаются друг от друга после первой кодировки, хотя почему до сих пор для меня загадка. Я полагаю, что это проблема отсутствия некоторых таблиц символов или вспомогательной библиотеки, потому что кажется, что что-то между 2.7.2 и 2.7.3 не могло бы объяснить такое поведение. В системах, где он работает правильно, при печати сущности Юникод отображается символ китайского языка, но в системе, где это не так, указывается местозаполнитель.
Это оставляет меня в моём вопросе: кажется ли кому-либо такая проблема знакомой, или у кого-то есть идея, какие вспомогательные библиотеки мне могут отсутствовать в системе, в которой есть проблема?
1 ответ
Проблема здесь в том, что декодирование UTF-7 по какой-то причине возвращает вам недопустимые символы Unicode.
Это в основном не задокументировано, что происходит, когда у вас есть unicode
объект с недопустимыми символами в нем. C API в основном просто говорит вам "не делайте этого, иначе все сломается". Python API не упоминает об этом, потому что это должно быть невозможно, если вы не сделали что-то неопределенное с C API, что уже описано.
Если, конечно, ошибка во встроенных кодеках не приводит к чему-то неопределенному от вашего имени. Что, кажется, то, что здесь происходит.
Одна из вероятных причин того, что вы видите это на некоторых платформах, но не на других, заключается в том, что все рабочие платформы используют узкий Unicode, то есть эта проблема не может возникнуть. (Вы не можете иметь кодовую точку > 0x10FFFF
на платформе, где кодовые точки составляют всего 2 байта, за исключением использования суррогатных байтов UTF-16, так что вы, вероятно, получите исключение - или игнорирование - при суррогатном кодировании.)
Тот факт, что незаконный характер вы получаете \U089d89df
и символ, который вы получаете на Mac (где системная сборка Python является узко-Unicode) \u89df
, довольно наводит на мысль о некотором коде, использующем ярлык где-то, который предполагает узкий Unicode. Но чтобы на самом деле отследить ошибку, вам нужно просмотреть несколько мест в исходном коде и сравнить, как Python построен на каждой платформе (узкая-широкая может быть не единственная разница), и / или просмотреть баги и логи изменений…
И в конечном итоге, если вы хотите работать в системах Ubuntu с этой сборкой Python, если вы не хотите писать свой собственный модуль C, вам придется обойти эту ошибку, верно?
Так что вы, вероятно, просто ищете простой обходной путь. В этом случае это должно работать:
decoded = u''.join(c for c in decoded if ord(c) <= 10FFFF)
Это удаляет любые символы, чья кодовая точка больше, чем самый большой допустимый символ Unicode. Это должно решить проблему везде, где она существует, и быть безвредной (за исключением некоторого потраченного времени процессора) в противном случае.
Для многих приложений вам действительно нужно иметь дело только с символами BMP, и что-либо в дополнительной и частной плоскостях с большей вероятностью будет ошибкой, чем фактические данные, поэтому это может быть проще или надежнее в использовании 0xFFFF
вместо 0x10FFFF
,