Как обрабатывать недекодируемые имена файлов в Python?

Мне бы очень хотелось, чтобы мое приложение Python работало исключительно со строками Unicode внутри. В последнее время у меня все шло хорошо, но я столкнулся с проблемой обработки путей. POSIX API для файловых систем не является Unicode, поэтому возможно (и на самом деле довольно часто) файлы имеют «недекодируемые» имена: имена файлов, которые не закодированы в заявленной кодировке файловой системы.

В Python это проявляется как смесь объектов unicode и str, возвращаемых из os.listdir().

>>> os.listdir(u'/path/to/foo')
[u'bar', 'b\xe1z']

В этом примере символ '\xe1' закодирован в Latin-1 или чем-то подобном, даже если (гипотетическая) файловая система сообщает sys.getfilesystemencoding() == 'UTF-8' (в UTF-8 этим символом будут два байта '\xc3\xa1'). По этой причине вы получите UnicodeError повсюду, если попытаетесь использовать, например, os.path.join() с путями Unicode, потому что имя файла не может быть декодировано.

Python Unicode HOWTO предлагает следующий совет относительно путей в Unicode:

Обратите внимание, что в большинстве случаев следует использовать API-интерфейсы Unicode. Bytes API следует использовать только в системах, где могут присутствовать недекодируемые имена файлов, то есть в системах Unix.

Поскольку меня в основном интересуют системы Unix, означает ли это, что я должен перестроить свою программу, чтобы иметь дело только со строками байтов для путей? (Если да, то как я могу поддерживать совместимость с Windows?) Или есть другие, лучшие способы борьбы с недекодируемыми именами файлов? Являются ли они настолько редкими «в дикой природе», что я должен просто попросить пользователей переименовать их чертовы файлы?

(Если лучше просто иметь дело со строками байтов внутри, у меня есть дополнительный вопрос: как хранить строки байтов в SQLite для одного столбца, сохраняя при этом остальные данные в виде понятных строк Unicode?)


person adrian    schedule 04.08.2010    source источник


Ответы (2)


У Python есть решение проблемы, если вы хотите перейти на Python 3.1 или более позднюю версию:

PEP 383 — Недекодируемые байты в интерфейсах системных символов.

person Mark Tolonen    schedule 06.08.2010
comment
Спасибо! Я не знал об этом PEP. Это довольно умное решение. - person adrian; 08.08.2010

Если вам нужно хранить строки байтов в БД, предназначенной для UNICODE, то, вероятно, проще записать строки байтов, закодированные в шестнадцатеричном формате. Таким образом, строку в шестнадцатеричном кодировании можно безопасно хранить как строку Unicode в базе данных.

Что касается проблемы с путями UNIX, насколько я понимаю, для имен файлов не применяется какая-либо конкретная кодировка, поэтому вполне возможно использовать Latin-1, KOI-8-R, CP1252 и другие в различных файлах. Это означает, что каждый компонент пути может иметь отдельную кодировку.

У меня возникло бы искушение попытаться угадать кодировку имен файлов, используя что-то вроде модуль chardet. Конечно, нет никаких гарантий, поэтому вам все равно придется обрабатывать исключения, но у вас будет меньше некодируемых имен. Некоторое программное обеспечение заменяет некодируемые символы на ? который необратим. Я бы предпочел, чтобы они были заменены на \xdd или \xdddd, потому что при необходимости их можно изменить вручную. В некоторых приложениях можно представить строку пользователю, чтобы он мог ввести символы Юникода для замены некодируемых.

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

person Michael Dillon    schedule 04.08.2010
comment
+1 за первый абзац - лучший способ справиться с недекодируемыми данными - избегать их декодирования, если это вообще возможно. Просканируйте список и закодируйте все, что является объектом Unicode, обратно в строку байтов, используя кодировку файловой системы. Существующие недекодируемые строки байтов должны оставаться нетронутыми. - person detly; 06.08.2010
comment
Да; Спасибо за совет. Я решился и полностью переключился на пути байтовых строк (по крайней мере, для Python 2.x). Для записи, обертывание объектов str в объекты буфера перед их сохранением в SQLite предотвращает их автоматическое декодирование как UTF-8. - person adrian; 08.08.2010