#python #python_3x #unicode #python_2x
У меня нет понимания как работать с путями в которых есть русские символы. Возникает несколько под-вопросов: После применения os.walk(), к примеру подав sys.argv и получения root, subdirectories, filenames в какой будут кодировке строки в этих трех переменных? В какую кодировку я должен сконвертировать строки из п.1, чтобы суметь открыть файл с помощью встроенной функции open()? В какой кодировке я должен конвертировать строку, чтобы открыть файл по записи? В какой кодировке имена файлов получаемые os.listdir()? Для удаления файла с помощью os.remove в какой кодировке должна быть строка (путь к файлу) после прочтения этой строки из файла с UTF-8 кодировкой? Интересует ветка Python 3.x, но также интересно и как это делается в Python 2.x
Ответы
Ответ 1
Кратко: на Питоне 3 sys.argv приходит уже как текстовая (Unicode) строка, поэтому ничего с ней делать не надо: передавайте как есть в os.walk(), open() и всё будет работать (даже если locale сломана). На Питоне 2 sys.argv это байты. На *nix системах это родной интерфейс, поэтому снова ничего делать не надо, а просто передавать как есть. На Windows, на Питоне 2, в общем случае следует руками получить sys.argv как Unicode, потому что если символы, заданные в командной строке, непредставимы в ANSI кодировке (например, cp1251), то sys.argv повреждённая до Питона доходит. если вы уверены, что ввод всегда в Виндовой codepage представим, то просто argv0 = sys.argv[0].decode(sys.getfilesystemencoding()) используйте если не уверены, то используйте GetCommandLineW() и CommandLineToArgvW(), чтобы произвольные символы поддерживать без потерь. Если помимо использования как аргументов open(), вы ещё хотите дальше с ними что-то делать, например, напечатать в консоль/терминал, тогда на Windows ставьте win-unicode-console пакет, начиная с Python 3.6 похожая функциональность встроена (PEP 528). На POSIX, если есть непредставимые в текущей локали имена файлов(пути могут быть произвольным набором байтов кроме b'\0' и b'\x2f'), то используйте os.fsencode() чтобы получить исходные байты на Питоне 3 (дальше как текст такие строки не следуют передавать так в них возможны lone surrogates из-за surrogateescape обработчика ошибок из PEP 383). После применения os.walk(), к примеру подав sys.argv и получения root, subdirectories, filenames в какой будут кодировке строки в этих трех переменных? В какую кодировку я должен сконвертировать строки из п.1, чтобы суметь открыть файл с помощью встроенной функции open()? В какой кодировке я должен конвертировать строку, чтобы открыть файл по записи? В какой кодировке имена файлов получаемые os.listdir()? [выделение моё] Если параметры и результат имеют тип unicode (Python 2 или str на Python 3), то слово кодировка неприменимо. Например, объект u"abc" в Питоне это неизменяемая последовательность Unicode codepoints (U+0061, U+0062, U+0063). Как он в памяти представлен не имеет значения, до тех пор пока эта абстракция не нарушена (что может произойти на так называемых narrow Python 2 сборках, когда астральные символы такие как U+1F468 могут занимать больше одного места—это недостаток реализации и всё равно не имеет отношения к кодировкам). Имея текст, можно его закодировать в байты, используя самые разные кодировки: >>> u'я'.encode('cp866') b'\xef' >>> u'я'.encode('cp1251') b'\xff' >>> u'я'.encode('utf-8') b'\xd1\x8f' Как видно из примера, разные кодировки могут один и тот же текст представлять используя разные последовательности байтов. Сама строка u'я' (объект в памяти) не имеет никакой кодировки (это просто последовательность состоящая из одного символа: U+044F в данном случае). Слово кодировка применимо, когда мы хотим преобразовать из текста в последовательность байтов или наоборот из байтов в текст: >>> b'\xef'.decode('cp866') u'я' >>> b'\xef'.decode('latin-1') u'ï' Видно что один и тот же байт может представлять разный текст в разных кодировках (то есть сам по себе байт не имеет никакой кодировки). На POSIX системе можно в качестве имени файла использовать любую последовательность байтов (кроме нуля b'\x00' и слеша b'\x2f'), например, b'\x01\x02\x03', то есть может не существовать осмысленного текста, соответствующего имени файла изначально. На практике, если пользователи используют locale с разными кодировками или внешние диски с файловыми системами, не хранящими используемую кодировку, то чтобы с этой кашей байтов безопасно работать можно или передавать байты как есть или (как это Питон 3 делает) придумать схему, которая позволяет декодировать в Unicode без потерь (surrogateescape обработчик ошибок). Формально, существует sys.getfilesystemencoding(), которая является кодировкой, используемой Питоном, чтобы интерпретировать имена файлов, аргументы командной строки, переменные окружения. Но (как показывают примеры выше) не всегда понятие кодировка может быть использовано (и даже если имена являются осмысленным текстом в какой-то кодировке, то не всегда эта кодировка совпадает с sys.getfilesystemencoding()—по разным причинам на разных системах). К примеру, sys.getfilesystemencoding() всегда равна utf-8 на Python 3.6 на Windows (PEP 529), что отличается как от ANSI так и OEM code pages, т.к., для фактического выполнения файловых операций используется системное Unicode API, которое позволяет использовать символы непредставимые в этих кодировках. UTF-8 кодировка также не имеет отношения к тому как Windows хранит имена файлов (UCS-2 или UTF-16). Использование какой-то одной кодировки для представления имён файлов невозможно в общем случае. Корректная работа с именами файлов не требует знания кодировок, используемых разными частями системы. Повторюсь: чтобы поддерживать самый общий случай, используйте Unicode на Питоне 3 на всех системах, возможно отслеживая lone surrogates на POSIX из-за surrogateescape, если вы хотите имена файлов в других контекстах использовать (например, по сети послать). Используйте Unicode API на Windows (на любой версии Питона). И для простоты передавайте имена файлов как есть (байты) на POSIX на Питоне 2. На Питоне 3, если вы передадите Unicode в os.walk(), os.listdir(), то и результат всегда будет Unicode. На Питоне 2, это не всегда так. open(), etc принимают как Unicode так и байты. Для удаления файла с помощью os.remove в какой кодировке должна быть строка (путь к файлу) после прочтения этой строки из файла с UTF-8 кодировкой? Если у вас файл содержит текст, закодированный с помощью UTF-8 кодировки, то вы можете использовать io.open() (встроена на Питоне 3 как просто open()), чтобы прочитать его содержимое как Unicode: #!/usr/bin/env python2 import io import sys with io.open('files_to_remove.txt', encoding='utf-8') as file: for path in file: path = path.strip() try: os.remove(path) except OSError, e: print >>sys.stderr, "warning: can't remove %r, reason: %s" % (path, e)Ответ 2
os.walk(), как и os.listdir(), даст на выходе данные в том же формате, что и входные [0]. Самый надёжный и простой способ – всегда использовать юникод. Гарантировать это можно, передавая в os.walk() путь в виде юникодовой строки: # Python 2: os.walk(u'.') # Python 3: os.walk('.') Функция open() затем примет имена файлов для открытия без каких либо изменений, потому что на данном этапе это будут юникодовые строки. [0]: https://docs.python.org/3/howto/unicode.html#unicode-filenames
Комментариев нет:
Отправить комментарий