На этом шаге мы рассмотрим способы решения этой задачи.
Ваша программа получила список содержимого каталога, но когда она попыталась вывести эти имена файлов, то упала с исключением
UnicodeEncodeError и загадочным сообщением "surrogates not allowed".
При выводе имен файлов неизвестного происхождения используйте преобразование, чтобы избежать ошибок:
def bad_filename(filename): return repr(filename)[1:-1] try: print(filename) except UnicodeEncodeError: print(bad_filename(filename))
Этот рецепт решает редкую, но очень раздражающую проблему, которая касается программ, работающих с файловой системой. По умолчанию Python предполагает, что все имена файлов закодированы согласно установке, которая возвращается функцией sys.getfilesystemencoding(). Однако некоторые файловые системы не заставляют соблюдать это ограничение, позволяя создавать файлы с неправильной кодировкой. Это не частый случай, но все же есть опасность, что некий пользователь сделает что-то глупое и случайно создаст такой файл (например, передаст неправильное имя файла функции open() в какой-то забагованной программе).
При выполнении команд типа os.listdir() неправильные имена файлов загоняют Python в безвыходную ситуацию. С одной стороны, он не может просто отбросить неправильное имя. С другой стороны, он не может превратить имя файла в правильную текстовую строку. Python действует так: берет недекодируемое байтовое значение \xhh в имени файла и отображает его в так называемую "суррогатную кодировку", представленную символом Unicode \udchh. Вот пример того, как неправильный список содержимого каталога может выглядеть, если он содержит имя файла bäd.txt, закодированное в Latin-1 вместо UTF-8:
>>> import os >>> files = os.listdir('.') >>> files ['spam.py', 'b\udce4d.txt', 'foo.txt'] >>>
Если у вас есть код, который манипулирует именами файлов или даже передает их функциям (таким как open()), все работает нормально. Вы попадете в неприятности только в ситуациях, где вы хотите вывести имя файла (вывод, логирование и т. п.). Ваша программа упадет, если вы захотите вывести показанный выше список:
>>> for name in files: print(name) spam.py Traceback (most recent call last): . . . . UnicodeEncodeError: 'utf-8' codec can't encode character '\udce4' in position 1: surrogates not allowed >>>
Причина падения в том, что символ \udce4 не является валидным в Unicode. Это вторая половина двухсимвольной комбинации, известной как "суррогатная пара". Поскольку первая часть отсутствует, это не валидный Unicode. Поэтому единственный способ успешно произвести вывод - предпринять корректирующее действие, если встретится неправильное имя файла. Например:
>>> for name in files: try: print(name) except UnicodeEncodeError: print(bad_filename(name)) spam.py b\udce4d.txt foo.txt >>>
Выбор того, что будет делать функция bad_filename(), во многом зависит от вас. Другая возможность - как-то перекодировать значение:
def bad_filename(filename): temp = filename.encode(sys.getfilesystemencoding(), errors='surrogateescape') return temp.decode('latin-1')
При использовании этой версии вы получите следующее:
>>> for name in files: try: print(name) except UnicodeEncodeError: print(bad_filename(name)) spam.py bad.txt foo.txt >>>
Этот рецепт наверняка будет проигнорирован большинством. Однако если вы пишете критически важные скрипты, которым нужно надежно работать с именами файлов и файловой системой, об этом стоит подумать. В противном случае вы можете столкнуться с ситуацией, когда вам придется сражаться с какой-то непонятной ошибкой.
На следующем шаге мы рассмотрим добавление или изменение кодировки уже открытого файла.