Поскольку в Python 3.X все обычные строки состоят из символов Юникода, имена каталогов и файлов, возвращаемые функциями os.listdir, os.walk и glob.glob, в действительности являются строками Юникода. Это может иметь некоторые последствия, если каталоги содержат необычные имена, не поддающиеся декодированию.
Формально имена файлов могут содержать любые символы, поэтому в версии 3.X функция os.listdir может работать в двух режимах: если ей передать аргумент типа bytes, она будет возвращать кодированные имена файлов в виде строк байтов; если ей передать аргумент типа str, она будет возвращать имена файлов в виде строк Юникода, декодированных в соответствии с кодировкой, используемой файловой системой:
C:\…\PP4E\System\Filetools> python
>>> 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 и glob. glob за кулисами обращаются к функции os.listdir, от которой наследуют то же самое поведение. Функция os.walk обхода деревьев, например, вызывает os. listdir для каждого подкаталога — передача строки байтов в аргументе подавляет декодирование, вследствие чего в результате возвращается строка байтов:
>>> for (dir, subs, files) in os.walk(‘..’): print(dir)
…
..
..\Environment
..\Filetools
..\Processes
>>> for (dir, subs, files) in os.walk(b’..’): print(dir) …
b’..’
b’..\\Environment’
b’..\\Filetools’
b’..\\Processes’
Функция glob.glob также вызывает функцию os.listdir перед применением шаблонов имен, и поэтому тоже возвращает имена в виде недеко- дированных строк байтов, когда получает строку байтов в аргументе:
>>> glob.glob(‘.\*’)[:3]
[‘.\\bigext—out.txt’, ‘.\\bigext—tree.py’, ‘.\\bigpy—dir.py’]
>>>
>>> glob.glob(b’.\*’)[:3]
[b’.\\bigext—out.txt’, b’.\\bigext—tree.py’, b’.\\bigpy—dir.py’]
Передавая имена в виде обычных строк (например, посредством аргумента командной строки), вы можете столкнуться с необходимостью преобразовывать обычные строки в строки байтов, с целью подавить декодирование:
>>> name = ‘.’
> >> os.listdir(name.encode())[:4]
[b’bigext-out.txt’, b’bigext-tree.py’, b’bigpy-dir.py’, b’bigpy-path.py’]
Таким образом, если каталоги могут содержать имена, не поддающиеся декодированию с использованием кодировки, используемой по умолчанию, вам может потребоваться передавать этим инструментам строки байтов, чтобы избежать ошибок, связанных с кодированием Юникода. В ответ вы будете получать строки байтов, которые могут оказаться менее читаемыми при выводе, но это убережет вас от ошибок при обходе каталогов и файлов.
Такой подход может оказаться особенно полезным в системах, где используются простейшие кодировки, такие как ASCII или Latin-1, но могут иметься файлы с именами в произвольных кодировках, скопированными с других компьютеров, из Интернета и так далее. В зависимости от ситуации для подавления некоторых ошибок кодирования можно использовать также обработчики исключений.
Пример того, какое это может иметь значение, мы увидим в первом разделе главы 6, где недекодируемое имя каталога вызывает появление ошибки при выводе во время полного сканирования диска (хотя данная ошибка относится скорее к функции вывода, чем к операции декодирования).
Обратите внимание, что встроенная функция open также может принимать имена открываемых файлов как в виде строк str Юникода, так и в виде строк байтов bytes, однако она использует этот аргумент, только чтобы дать начальное имя файлу, — порядок же обработки содержимого файла определяется дополнительным аргументом режима. Возможность передавать строку байтов в качестве имени файла позволяет использовать произвольные кодированные имена.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011