Вывод имен файлов с символами Юникода

vyvod imen fajlov s simvolami junikoda Законченные системные программы

Одна тонкость, прежде чем двинуться дальше: обратите внимание на, казалось бы, излишнюю конструкцию обработки исключений в функции tryprint из примера 6.4. Когда я в первый раз попытался просканировать весь диск с помощью сценария, приведенного в предыдущем разделе, этот сценарий завершился с ошибкой декодирования символов Юникода при попытке вывести имя каталога сохраненной вебстраницы. Добавление обработчика исключения позволило просто пропустить этот каталог.

Этот случай наглядно демонстрирует тонкую, но имеющую большое практическое значение проблему: ориентированность Python 3.X на Юникод распространяется и на имена файлов, даже в случае простого их вывода. Как мы узнали в главе 4, имена файлов могут содержать произвольный текст, поэтому функция os.listdir способна возвращать имена файлов в двух различных представлениях — она возвращает декодированные строки Юникода, если получает аргумент типа str, и кодированную строку байтов, если получает аргумент типа bytes:

>>> import os

>>> os.listdir(‘.’)[:4]

[‘bigext-tree.py’, ‘bigpy-dir.py’, ‘bigpy-path.py’, ‘bigpy-tree.py’]

>>> os.listdir(b’.’)[:4]

[b’bigext-tree.py’, b’bigpy-dir.py’, b’bigpy-path.py’, b’bigpy-tree.py’]

Обе функции, os.walk (используется в примере 6.4) и glob.glob, наследуют это поведение при возвращении имен файлов и каталогов, потому что внутри они используют функцию os.listdir. Во всех этих функциях передача строки байтов в аргументе подавляет декодирование символов Юникода в именах файлов и каталогов. Передача обычной строки предполагает, что имена файлов могут быть декодированы при применении кодировки, используемой файловой системой.

Причина, по которой данная особенность может иметь важное значение для сценария из этого раздела, состоит в том, что версия, выполняющая поиск по всему жесткому диску, в конце концов может столкнуться с недекодируемыми именами файлов (например, ранее сохраненная веб-страница с необычным именем), что приводит к возбуждению исключения при попытке вывести их с помощью функции print. Ниже приводится упрощенный пример, выполняемый в окне консоли Windows, который воспроизводит ошибку:

>>> root = r’C:\py3000’

>>> for (dir, subs, files) in os.walk(root): print(dir)

C:\py3000

C:\py3000\FutureProofPython PythonInfo Wiki_files

C:\py3000\Oakwinter_com Code » Porting setuptools to py3k_files Traceback (most recent call last):

File “<stdin>”, line 1, in <module>

File “C:\Python31\lib\encodings\cp437.py”, line 19, in encode

return codecs.charmap_encode(input,self.errors,encoding_map)[0]

UnicodeEncodeError: ‘charmap’ codec can’t encode character ‘\u2019’ in position 45: character maps to <undefined>

(UnicodeDecodeError: кодек charmapне может преобразовать символ ‘\ u2019’ в позиции 45: отображается в символ <undefined>)

Один из способов выхода из этого затруднительного положения состоит в том, чтобы передавать имя корневого каталога в виде строки типа bytes, — это подавляет выполнение операции декодирования имен файлов в функции os.listdir, вызываемой функцией os.walk, и эффективно ограничивает область действия последующих операций вывода простыми байтами. Так как в этом случае операциям вывода не приходится иметь дело с кодировками, они выполняются без ошибок. Декодирование строк в байты вручную перед выводом тоже может помочь, но результаты получаются немного другими:

>>> root.encode()

b’C:\\py3000’

>>> for (dir, subs, files) in os.walk(root.encode()): print(dir)

b’C:\\py3000’

b’C:\\py3000\\FutureProofPython PythonInfo Wiki_files’

b’C:\\py3000\\Oakwinter_com Code \xbb Porting setuptools to py3k_files’ b’C:\\py3000\\What\x92s New in Python 3_0 \x97 Python Documentation’

>>> for (dir, subs, files) in os.walk(root): print(dir.encode())

b’C:\\py3000’

b’C:\\py3000\\FutureProofPython PythonInfo Wiki_files’

b’C:\\py3000\\Oakwinter_com Code \xc2\xbb Porting setuptools to py3k_files’ b’C:\\py3000\\What\xe2\x80\x99s New in Python 3_0 \xe2\x80\x94 Python Documentation’

К сожалению, при любом подходе все имена каталогов, которые выводятся в процессе обхода, отображаются как непонятные строки байтов. Чтобы не отказываться от обычных строк, обеспечивающих более высокую удобочитаемость, я выбрал вариант реализации с обработчиком исключений. Это позволяет полностью исключить проблемы:

>>> for (dir, subs, files) in os.walk(root):

try:

print(dir)

except UnicodeEncodeError:

print(dir.encode()) # или просто пропустить, если encode

# может потерпеть неудачу

C:\py3000

C:\py3000\FutureProofPython PythonInfo Wiki_files

C:\py3000\Oakwinter_com Code » Porting setuptools to py3k_files b’C:\\py3000\\What\xe2\x80\x99s New in Python 3_0 \xe2\x80\x94 Python Documentation’

Странно, но похоже, что ошибка связана скорее с выводом, чем с кодированием символов Юникода в именах файлов, — поскольку при работе с именами файлов никаких ошибок не возникает, пока не предпринимаются попытки вывести их, следовательно, они были успешно декодированы в момент первоначального преобразования их в строки. Именно поэтому достаточно обернуть в инструкции try вызовы функции print, в противном случае ошибка возникала бы раньше.

Более того, эта ошибка не возникает, если стандартный поток вывода сценария перенаправить в файл, на уровне командной оболочки (bigexttree.py c:\ > out) или в вызове самой функции print (print(dir, file=F)). В последнем случае чтение выходного файла должно выполняться в двоичном режиме, так как попытка вывести в окно консоли содержимое файла, открытого в текстовом режиме, приведет к той же ошибке (и снова, ошибка не возникает, пока не будет предпринята попытка вывода). Фактически программный код, который терпит неудачу при запуске в окне программы Командная строка (Command Prompt) в Windows, работает без ошибок в графическом интерфейсе IDLE, на той же самой платформе, — графический интерфейс IDLE, реализованный на основе библиотеки tkinter, выполняет обработку отображаемых символов, что не делается, когда символы выводятся в поток стандартного вывода, подключенный к окну терминала:

>  >> import os # запускайте в IDLE (в графическом интерфейсе на базе tkinter), # а не в системной командной оболочке

>  >> root = r’C:\py3000’

>  >> for (dir, subs, files) in os.walk(root): print(dir)

C:\py3000

>  :\py3000\FutureProofPython PythonInfo Wiki_files

C:\py3000\Oakwinter_com Code » Porting setuptools to py3k_files C:\py3000\What’s New in Python 3_0 — Python Documentation_files

Другими словами, исключение возникает только при выводе в окно командной оболочки, уже после того, как будет создана строка с именем файла. Это свидетельствует о выполнении дополнительных преобразований в подсистеме вывода Python, что никак не связано с наличием символов Юникода в именах файлов. Однако, за неимением места для дальнейших рассуждений, нам придется удовлетвориться констатацией факта, что наш обработчик исключений позволяет обойти проблему вывода. Однако вам следует помнить о проблемах декодирования символов Юникода в именах файлов — на некоторых платформах может потребоваться в этом сценарии передавать функции os.walk строки байтов, чтобы предотвратить ошибки декодирования имен файлов.[XIX]

Механизм поддержки Юникода в версии 3.1 все еще остается относительно новым, поэтому обязательно проверяйте наличие подобных ошибок в вашей системе и в вашей версии Python. Кроме того, дополнительную информацию по работе с именами файлов, содержащими символы Юникода, можно найти в руководствах по языку Python, а о поддержке Юникода вообще — в книге «Изучаем Python». Как отмечалось выше, сценарии должны открывать текстовые файлы в двоичном режиме, если они могут содержать недекодируемое содержимое. Может показаться странным, что проблемы, связанные с поддержкой Юникода, могут так отражаться на простейших операциях вывода, как в данном примере, но такова жизнь в прекрасном мире Юникода. При этом никаких проблем, связанных с Юникодом, не возникает в большой доле реальных сценариев, включая те, что мы будем рассматривать в следующем разделе.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий