/привет/мир/etc
Непериодические заметки о программировании
четверг, 10 июля 2014 г.
Строки символов Unicode в Python 2 и Python 3
Винсент: Знаешь, что самое забавное в Европе?
Джулс: Что?
Винсент: Такие маленькие отличия. Там вроде все то же самое, что и здесь, но чуть-чуть отличается.
Как известно, ключевым отличием Python 3.x от Python 2.x является переориентация языка и стандартных библиотек со строк байтов на строки символов Unicode. Когда я решил повнимательнее посмотреть на это различие, пришлось копнуть вглубь и вширь, а результаты моих раскопок я оформил в статью, которую предлагаю вашему вниманию.
В Python 2 | В Python 3 |
---|---|
строковый литерал получает тип str | строковый литерал получает тип str |
тип str представляет собой строку байтов | тип str представляет собой строку символов Unicode |
для представления строки символов Unicode используется тип unicode | для представления строки байтов используется тип bytes |
для представления строки unicode в нужной кодировке используется метод unicode.encode(кодировка) , возвращающий строку байтов str | для представления строки str в нужной кодировке используется метод str.encode(кодировка) , возвращающий строку байтов bytes |
для преобразования строки байтов str в строку unicode используется метод str.decode(кодировка) , возвращающий строку unicode | для преобразования строки байтов bytes в строку str используется метод bytes.decode(кодировка) , возвращающий строку str |
системная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, ASCII | системная кодировка по умолчанию, она же кодировка исходных файлов по умолчанию, UTF-8 |
при записи в файл строки по умолчанию преобразуются в системную кодировку по умолчанию | при записи в файл строки по умолчанию преобразуются в кодировку, определяемую пользовательскими настройками |
идентификаторы в программе содержат только символы ASCII, не могут использовать никаких других букв, кроме латинских | идентификаторы в программе содержат символы Unicode, могут использовать буквы других алфавитов, кроме латинского |
Мои эксперименты я ставлю под ОС Windows 7 в стандартной консоли.
Для разминки, несколько манипуляций со строковыми литералами и переменными в Python 2:
Теперь посмотрим на кодировки, используемые в Python 2 и Python 3 по умолчанию:
- Системная кодировка по умолчанию (system default encoding), возвращается функцией sys.getdefaultencoding() . Это кодировка исходных текстов Python по умолчанию, она же используется по умолчанию для кодирования и декодирования строк unicode .
- Кодировки стандартных потоков ввода, вывода и ошибок — файловых объектов, атрибуты sys.stdin.encoding , sys.stdout.encoding и sys.stderr.encoding , соответственно. Строки символов Unicode, посылаемые в канал вывода, преобразуются в соответствующую кодировку (или в системную кодировку по умолчанию, если атрибут encoding is None ).
- Кодировка имен файлов в файловой системе, возвращается функцией sys.getfilesystemencoding() . Имена файлов, представленные в программе на Python как строки символов Unicode, преобразуются в эту кодировку для взаимодействия с файловой системой (или в системную кодировку по умолчанию, если sys.getfilesystemencoding() возвращает None ).
- Кодировка для текстовых данных, заданная пользовательскими настройками, возвращается функцией locale.getpreferredencoding(False) .
Кодировки в Python 2:
Кодировки в Python 3:
Как видим, системной кодировкой по умолчанию для Python 2 является ascii , а для Python 3 — utf-8 . И это единственное обнаруженное различие.
Нам, русским, очень «повезло» с обилием кириллических кодировок в Windows. Работая в консоли Windows, по умолчанию мы имеем дело с кириллической кодировкой cp866 . Работая с текстовым файлом в Блокноте, по умолчанию мы работаем в кириллической кодировке cp1251 . Имена файлов, использующие русские символы, в файловой системе Windows представлены в кодировке mbcs (multi-byte character set), — это двухбайтовая кодировка, которая позволяет представить подмножество символов Unicode (UTF-16?).
Вооружившись знанием об используемых по умолчанию кодировках, попробуем в интерактивном режиме Pyhton вводить и выводить строки, включающие нелатинские символы.
Фрагмент интерактивного сеанса Python 2:
Что я только что сделал?
Команды вводятся через стандартный входной поток, использующий кодировку cp866 ( sys.stdin.encoding ). Таким образом, в первом предложении присваивания литерал u’Привет world’ преобразуется в строку unicode из кодировки cp866 и полученная строка unicode присваивается переменной u . Далее я проверил тип и значение переменной u и увидел, что русские буквы представлены двухбайтовыми кодами Unicode. Наконец, предложение print посылает в стандартный выходной поток данную строку unicode и строку str , полученную преобразованием строки u в кодировку cp866 . Результат вывода обеих строк одинаков, поскольку строка unicode неявно преобразуется при выводе в стандартый выходной поток в кодировку cp866 ( sys.stdout.encoding ).
В Python 3 получим такой результат (предлагаю интерпретировать его самостоятельно):
В Python 2 для использования в скриптах не-ASCII символов нужно явно указывать кодировку исходного файла, поскольку системная кодировка по умолчанию, ascii , подразумевает использование в файле только символов ASCII. А в Python 3 исходный файл по умолчанию содержит символы Unicode в кодировке utf-8 , что позволяет использовать в нем практически любые символы без явного указания кодировки.
Файл hello.py в кодировке UTF-8 для Python 2:
Выполню его в консоли Windows:
Файл hello3.py в кодировке UTF-8 для Python 3:
Выполню его в консоли Windows:
Разница с работой скрипта hello.py в том, что теперь к кодировке стандартного потока вывода приводится значение типа str , и строка байтов bytes не интерпретируется как строка читабельных символов.
Следующий скрипт helloname.py демонстрирует ввод и вывод кириллических символов в Python 2, используя для их хранения строки unicode :
Выполню скрипт в консоли Windows:
Посмотрим теперь, что происходит при записи строк символов Unicode в файл. Если в Python 2 явно не преобразовывать выводимые в файл строки unicode в строки str с нужной кодировкой, то получим ошибку.
Выполню скрипт в консоли Windows:
При записи в файл Python 2 пытается преобразовать строку unicode в системную кодировку по умолчанию, ascii , поскольку атрибут encoding открытого нами файла имеет значение None . В кодировке ascii нельзя представить кириллические символы, из-за чего и возникает ошибка.
Во избежание ошибок, при записи строк unicode в файл нужно явно приводить их к желаемой кодировке:
Выполню исправленный скрипт в консоли Windows:
Аналогичный эксперимент с Python 3 показывает, что строки пишутся в файл в кодировке, определяемой локалью пользователя!
Выполняю скрипт в консоли Windows:
Выше мы видели, что на русифицированной Windows функция locale.getpreferredencoding(False) возвращает ‘cp1251’ . Именно с этой кодировкой открывается новый файл по умолчанию, и, как следствие, к ней приводятся строки, записываемые в этот файл.
С кодировкой файла cp1251 запись в него смешанной латино-кириллической строки проходит на ура, а вот попытка записи кандзи вместе с кириллицей приводит к уже знакомой нам ошибке:
Выполняю скрипт в консоли Windows:
Кодировка cp1251 не кодирует кандзи!
Хорошая новость в том, что в Python 3, в отличие от Python 2, при открытии файла можно явно указать кодировку файла. В эту кодировку и будут преобразовываться строки str при записи в файл; из этой кодировки будут преобразовываться в str читаемые из файла строки байтов.
Укажу явно кодировку открываемых файлов в скрипте hello32.py :
Выполняю скрипт в консоли Windows:
Как видим, скрипт пишет в файл и читает из файла строки в кодировке utf-8 .
В заключение, экзотический пример кода. Вследствие того, что в Python 3 системной кодировкой по умолчанию является utf-8 , в Python 3 можно использовать в идентификаторах не только латиницу, но и другие символы Unicode:
Сопровождать такой код и вносить в него изменения интернациональной команде разработчиков будет проблематично!
Проделанные сравнительные эксперименты не дали мне достаточно оснований, чтобы решительно встать на одну из сторон в священной войне между защитниками Python 2 и энтузиастами Python 3 :). Хотя Unicode-ориентированность Python 3 и то, как это сказывается на прикладном программировании, мне нравится.
Источник
Работаем с текстами на Python: кодировки, нормализация, чистка
Зачем эта статья?
Об обработке текстов на естественном языке сейчас знают все. Все хоть раз пробовали задавать вопрос Сири или Алисе, пользовались Grammarly (это не реклама), пробовали генераторы стихов, текстов. или просто вводили запрос в Google. Да, вот так просто. На самом деле Google понимает, что вы от него хотите, благодаря штукам, которые умеют обрабатывать и анализировать естественную речь в вашем запросе.
При анализе текста мы можем столкнуться с ситуациями, когда текст содержит специфические символы, которые необходимо проанализировать наравне с «простым текстом» (взять даже наши горячо любимые вставки на французском из «Война и мир») или формулы, например. В таком случае обработка текста может усложниться.
Вы можете заметить, что если ввести в поисковую строку запрос с символами с ударением (так называемый модифицирующий акут), к примеру «ó», поисковая система может показать результаты, содержащие слова из вашего запроса, символы с ударением уже выглядят как обычные символы.
Обратите внимание на следующий запрос:
Запрос содержит символ с модифицирующим акутом, однако во втором результате мы можем заметить, что выделено найденное слово из запроса, только вот оно не содержит вышеупомянутый символ, просто букву «о».
Конечно, уже есть много готовых инструментов, которые довольно неплохо справляются с обработкой текстов и могут делать разные крутые вещи, но я не об этом хочу вам поведать. Я не буду рассказывать про nltk, стемминг, лемматизацию и т.п. Я хочу опуститься на несколько ступенек ниже и обсудить некоторые тонкости кодировок, байтов, их обработки.
Откуда взялась статья?
Одним из важных составляющих в области ИИ является обработка текстов на естественном языке. В процессе изучения данной тематики я начал задавать себе вопросы, которые в конечном итоге привели меня к изучению кодировок, представлению текстов в памяти, как они преобразуются, приводятся к нормальной форме. Я плохо понимал эту тему в начале, потребовалось немало времени и мозгового ресурса, чтобы понять, принять и запомнить некоторые вещи. Написанием данной статьи я хочу облегчить жизнь людям, которые столкнутся с необходимостью чтения и обработки текстов на Python и самому закрепить изученное. А некоторыми полезными поинтами своего изучения я постараюсь поделиться в данной статье.
Важная ремарка: я не являюсь специалистом в области обработки текстов. Изложенный материал является результатом исключительно любительского изучения.
Проблема чтения файлов
Допустим, у нас есть файл с текстом. Нам нужно этот текст прочитать. Казалось бы, пиши себе такой вот скрипт для чтения из файла да и радуйся:
В файле содержится вот такое вот изречение:
что переводится с испанского как питон. Однако консоль OC Windows 10 покажет нам немного другой результат:
Сейчас мы разберёмся, что именно пошло не так и по какой причине.
Кодировка
Думаю, это не будет сюрпризом, если я скажу, что любой символ, который заносится в память компьютера, хранится в виде числа, а не в виде литерала. Это число определяется как идентификатор или кодовая позиция символа. Кодировка определяет, какое именно число будет ассоциировано с символом.
Предположим, у нас есть некоторый файл с неизвестным содержимым, и нам нужно его прочитать, однако мы не знаем, какая у файла кодировка. Попробуем декодировать содержимое файла.
Посмотрим на результат:
Очень интересно, ничего непонятно. По умолчанию Python использует кодировку utf-8, но видимо запись в файл происходила не с её помощью. Здесь нам придёт на помощь дополнительный параметр функции open — параметр encoding, который позволяет указать конкретную кодировку, в которой следует прочитать файл (или записывать в него). Попробуем перебрать несколько кодировок и найти подходящую.
Разные кодировки расшифровывают байты из файла по-разному, то есть разным кодовым позициям могут соотвествовать разные символы. Пример примитивный, несложно догадаться, что истинная кодировка файла — это utf-16.
Важный поинт: при записи и чтении из файлов следует указывать конкретную кодировку, это позволит избежать путаницы в дальнейшем.
Ошибки, связанные с кодировками
При возникновении ошибки, связанной с кодировками, интерпретатор выдаст одно из следующих исключений:
UnicodeError . Это общее исключение для ошибок кодировки.
UnicodeDecodeError . Данное исключение возбуждается, если встречается кодовая позиция, которая отсутствует в кодировке.
UnicodeEncodeError . А это исключение возбуждается, когда символ, который необходимо закодировать, незнаком для кодировки.
Попытка выполнения вот такого кода (в файле всё ещё содержится испанский питон):
даст нам следующий результат:
Кодировка ASCII не поддерживает никакой алфавит, кроме английского. Поэтому декодирование символа «ó» вызывает у ASCII сложности. Однако Python всемогущ и есть механизм, который позволяет обработать ошибки кодировок. Это дополнительный параметр методов encode и decode — параметр errors . Он может принимать следующие значения:
Для обеих функций:
Обозначение
Суть
Значение по умолчанию. Несоотвествующие кодировке символы возбуждают исключения UnicodeError и наследуемые от него.
Несоответсвующие символы пропускаются без возбуждения исключений.
Только для метода encode :
Несоотвествующие символы заменяются на символ ?
Несоответствующие символы заменяются на соответсвующие значения XML.
Несоответствующие символы заменяются на определённые последовательности с обратным слэшем.
Несоответствующие символы заменяются на имена этих символов, которые берутся из базы данных Unicode.
Также отдельно выделены значения surrogatepass и surrogateescape .
Приведём пример использования таких обработчиков:
Важный поинт: если в текстах могут встретиться неожиданные для кодировки символы, во избежание возбуждения исключений можно использовать обработчики.
Cворачивание регистра
Сворачивание регистра — это попытка унифицировать текст любого представления к канонической форме. Например, приведение всего текста в нижний регистр. Также над текстом производятся некоторые преобразования (например, немецкая «эсцет» — «ß» — преобразуется в «ss»). В Python 3.3 появился метод str.casefold() , который как раз выполняет сворачивание регистра. Если текст содержит только символы кодировки latin1 , результат применения этого метода будет аналогичен методу str.lower() .
И по классике приведём пример:
В результате применённый метод не только привёл весь текст к нижнему регистру, но и преобразовал специфический немецкий символ.
Важный поинт: привести текст можно не только методом str.lower() , но и методом str.casefold() , который может выполнить дополнительные преобразования текста.
Нормализация
Нормализация — это полноценное приведение текста к единому представлению.
Чтобы обозначить важность нормализации, приведём простой пример:
Внешне два этих символа выглядят абсолютно одинаково. Однако если мы попытаемся вывести имена этих символов, как их видит интерпретатор Python’a, результат нас порядком удивит.
В Python есть отличный встроенный модуль, который содержит данные о символах Unicode, их имена, являются ли они цифрамии и т.п. (методы по типу str.isdigit() берут информацию из этих данных). Воспользуемся данным модулем, чтобы вывести имена символов, исходя из информации, которая содержится в базе данных Unicode.
Результат выполнения данного кода:
Итак, интерпретатор Python’a видит эти символы как два разных, но в стандарте Unicode они имеют одинаковое отображение.Такие символы называют каноническими эквивалентами. Приложения будут считать два этих символа одинаковыми, но не интерпретатор.
Посмотрим на ещё один пример:
Данные символы также будут являться каноническими эквивалентами. Из примера мы видим, что символ «é» в стандарте Unicodeможет быть представлен двумя способами, которые к тому же имеют разную длину. Символ «é» может быть представлен одним или двумя байтами.
Источник