понедельник, 7 декабря 2015 г.

Кодировки, юникод; Работа с кодировками и юникодом в Python

Кодировки (общее)



Кодировки - это то, счем мы имеем дело каждый день, работая с компьютером, планшетом, кпк и Т.Д. Среднестатистический пользователь сталкивается с кодировками только тогда, когда на экране вместо текста, вылазят курказябры и разные умляуты. Все это признак, что текст закодированный в одной кодировке, пытаются прочесть при помощи другой кодировки. Чаще всего это происходит в текстовых файлах, на интернет страницах или в текстах писем почтовиков.
Какие названия кодировок чаще всего попадаются нам на глаза? Это utf-8, cp1251, ascii. Реже - cp866, koi8-r, utf-16, а также такое понятие как unicode. В рамках данной темы, я не стану писать о непосредственной тематике кодировок, принципов их создания и работы. Это не является задачей этой темы. Для углубления в понятие кодировок, я дам несколько ссылок. Кпримеру перевод одного из хороших материалов по тематике кодировок, можно посмотреть тут. Еще подробно и последовательно про кодировки рассказываетсятут.

Я рекомендую вдумчиво изучить материалы по данным ссылкам, Т.К. Всех начинающих программистов, работа с кодировками, нередко вводит в ступор.

Естественно, я попробую привести сдесь упрощенную модель понимания кодировок. Эта модель будет абстрактной и условной, но она нацелена больше на понимание основ кодирования текста, а не на подробный разбор темы кодировок.

Для начала, нам необходимо помнить то, что компьютер не умеет хранить в себе буквы, он умеет хранить двоичный код, который состоит из 0 и 1. При помощи нулей и единиц, наши тексты зашифрованы в памяти компьютера. Вот тут и вступает в дело то, что мы называем кодировкой.

Давайте договоримся, условно, что код 0101, отвечает за русскую букву "а", а код 0011, за букву "б". Да простят меня за такое читающие сие. Теперь представим 3 разных кодировки. Скажем cp1251, koi8-r и cp866. Я там сказал, что какие-то коды отвечают за буквы? Давайте уточним, в кодировке cp1251, код 0101, отвечает за букву "а", а код 0011, за букву "б" и у нас в тексте записано слово "баба" двоичным кодом. Тоесть, когда мы будем использовать кодировку cp1251 и нам встретится слово в двоичном коде вида "0011 0101 0011 0101", кодировка по этим кодам, преобразует нам эти коды в слово с букв "баба". Все хорошо, но продолжим...

Скажем, что в кодировке koi8-r, кодам 0101 и 0011, соответствуют буквы "ю" и "я". Что произойдет, если мы попробуем прочесть этоже слово "0011 0101 0011 0101", при помощи кодировки koi8-r? Мы получим слово из букв "яюяю".

Допустим, что кодировка cp866, содержит в себе соответствие кодам "0101" и "0011", для букв "у" и "х". Если мы попробуем прочесть слово записанное двоичным кодом вида "0011 0101 0011 0101" при помощи кодировки cp866, мы получим слово "хуху".

Немножко проясняется? То, что мы назваем кодировкой, всего лишь таблица, где хранятся условные двоичные коды и символы(буквы), которые соответствуют этим кодам в конкретной кодировке. Историю о том, почему так получилось, читаем все по тем же ссылкам, которые я давал выше.

Какой мы можем сделать общий вывод? Если у нас будет текст, который будет содержать себе буквы в двоичном коде и будет предназначен для прочтения кодировкой cp1251, а мы попробуем прочесть его при помощи другой кодировки, мы получим совершенно нечитаемый и неправильный набор букв. Также помним про то, что далеко не все кодировки содержат в себе русские буквы. Кпримеру кодировка ascii русских букв не имеет вовсе, да что там, очень мало кодировок, которые содержат в себе русские символы. И при попытке прочесть текст, который закодирован cp1251 при помощи какой-то индусской кодировки, мы получим айсуконфлексы, юсуконфлексы и другие нечитаемые псевдографические умляуты.

Как быть? Если вы удосужились прочесть что-то по ссылкам выше, вы должны были прочесть про такое явление, как юникод(unicode). Это символьное отображение всех символов, которые существуют в письменности на земле. Кпримеру буква "а", будет записана, как "\u0430", а буква "Б", как "\u0431". Каждый символ имеет в юникоде свое обозначение. Акцентирую ваше внимание, что юникод не кодировка - это способ символьного отображения других символов, где каждый символ имеет строго свое индивидуальное обозначение. Из этого следует вывод, что при необходимости мы можем воспользоваться таблицей юникода и закодировать соответствующие символы(буквы) в двоичный код, который будет соответствовать выбранной кодировке. Также мы можем раскодировать символы с любой кодировки в юникод. Приметивный пример этого может выглядеть примерно так:

Допустим буква "а" в cp1251, записана двоичным кодом "0101". Для koi8-r - "1001". в юникоде - "\u0430".

Раскодируем букву "а" с cp1251 в юникод и закодируем в cp866.

(0101 -> \u0430 -> 1001)


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

Мы можем раскодировать текст с какой-то кодировки в универсальный юникод, где каждый символ будет иметь индивидуальное обозначение.

Мы можем закодировать текст из юникода в необходимую нам кодировку.


Кстати, вы могли заметить, что почти всегда, курказябрами становится русский, украинский, одним словом текст, который состоит не из латинских(английских) букв и символов. В то время, как английский текст, почти всегда, остается читаемым. Все это потому, что практически все кодировки были построены на основании ascii и коды для английских символов в них совпадают.


Глотком свежего воздуха, стало создание кодировки utf-32 на базе юникодовой таблицы. Историю сего явления, вы тоже можете прочесть по ссылкам выше. По определенным причинам, кодировку utf-32 убрали с горизонта и создали кодировку utf-16. Боюсь повториться, но в силу еще кое-каких причин, была создана кодировка utf-8 и она медленно вытеснила кодировку utf-16. Сейчас де-факто utf-8 является стандартом для кодирования текстов. Конечно, стандарт в нашем мире сотен кодировок - это хорошо, но часто встречаются проприетарные кодировки, которые использовались ранее и в в каких-то случаях так и не изменились. Хорошим примером этого, может стать windows, со своей кодировкой cp1251 для общего окружения и cp866, для работы с консолью. Тотже linux, который ранее использовал кодировку koi8-r, уже давно перешел на utf-8. macos тоже использует кодировку utf-8. А windows попрежнему хватается за мультикодировочность и не использует по умолчанию utf-8.

Последнее, что я скажу про кодировки вобщем, что мы можем наблюдать кодировку utf-8 с bom и без bom. В некоторых системах, чтение байтов с двоичным кодом, которые отвечают за какие-то символы в кодировках, может производиться слева на право, а в каких-то справа на лево. Наша буква "а", записанная двоичнім кодом "0101", может читаться слева на право "0101", также и справа на лево "1010", что дает нам несколько разный набор нулей и единиц. Если мы запишем эту букву в системе, которая читает этот код слева на право, а попытаемся прочесть в системе, которая читает этот код справа на лево, мы получим курказябры, умляуты, вопросительные знаки и Т.Д. Символ bom - это первый символ в текстовом пространстве, который сообщает, в каком порядке необходимо читать наш код, что спасает нас от разночтения в разных операционных системах. Для начала, я советую использовать utf-8 без bom, что избавит нас от необходимости учитывать первый символ bom. У нас ничего страшного, без символа bom, не произойдет.


Юникод и кодировки в Python



Для начала учтем то, что я буду выдавать дальнейшую информацию исходя из того, что все это происходит под OS Windows, но постараюсь давать необходимые комментарии в местах, где это будет актуально для других OS.

Нам необходимо запомнить, что все, что происходит в консоли, происходит в кодировке cp866. Это актуально для Windows. При использовании Linux и MacOS, консоль будет в кодировке utf-8. Определенные шаги, которые мы будем совершать с кодировками в Python, будут актуальны для Windows и неактуальны для других систем, и наоборот.


При создании файла, в котором мы будем писать код нашей программы, первой строкой мы вписываем следующее:

#-*- coding:utf-8 -*-

Это сообщит Python, что содержимое файла, хранится в кодировке utf-8. Никогда не пренебрегайте этой строкой, она очень важна!


Дальнейшие пляски вокруг создания юникодовых литералов и кодирования/декодирования ввода и вывода данных в консоли, будут актуальны для OS Windows. Консоль MacOs и Linux используют кодировку utf-8, что позволяет работать с русским текстом без кодирования/декодирования строк. Правда, что в некоторых случаях при разнообразных настройках OS Linux (Чаще) и в MacOs (реже), мы можем получать неожиданные результаты работы с кодировками. В случаях совсем уж непонятных результатов, мы методом тыка пытаемся определить, что же не так, а еще лучше гуглим на тему кодировок в необходимой OS, а также кодировок в определенной OS при программировании на Python.


Для начала, мы поговорим о юникоде в Python. Текст в юникоде имеет свой тип данных "unicode". Создать юникодовый литерал(строку), можно несколькими способами.

Во-первых, мы можем указать модификатор "u" перед кавычками литерала "" или '':

>>> u'а'

u'\u0430'

>>>

>>> a = u'а'

>>> type(a)



>>>


Если мы не укажем модификатор "u", мы получим строчный литерал типа str в кодировке консоли: Для Windows - cp866, для остальных OS, с большой долей вероятности, utf-8.

>>> b = 'а'

>>> b

'\xa0'

>>> type(b)



>>>


Как видим, при попытке вывести русскую букву без использования оператора print, оканчивается выводом некой символьной записи этой буквы, но если мы воспользуемся оператором print, все будет нормально:

>>> a

u'\u0430'

>>> b

'\xa0'

>>> print a

а

>>> print b

а

>>>


Нам необходимо запомнить, что оператор print, автоматически пытается вывести нам текст, подразумевая кодировку консоли - cp866 для Windows и utf-8 для других OS. Также, она автоматически кодирует unicode в кодировку консоли, что позволяет правильно отображать юникодовые литералы. И естественно, если нам попадется текст в кодировке cp1251, а мы попытаемся вывести его оператором print, мы получим курказябры, Т.К. print попытается вывести текст, ориентируясь на то, что он записан в кодировке консоли, что приводит нас к тому, что текст должен быть либо в кодировке, соответствующей кодировке консоли, либо в юникоде, либо мы должны явно раскодировать неподходящую для вывода кодировку в юникод.


В теории, если мы создадим юникодовый литерал, через print, мы должны получить нормальный читаемый текст, но как показала практика, в каких-то случаях юникодовый литерал, даже при использовании оператора print, вылазит курказябрами. Опять таки, в теории все, у кого консоль находится в utf-8, могут вовсе не забивать себе голову использованием всего богатства кодирований/декодирований для ввода и вывода данных в консоли. Для теста, как оно работать будет, выполните следующее (Актуально для MacOs и Linux):

a = 'Привет'

print a


В теории должно выйти на экран читаемым текстом.

a = u"Привет"

print a


По хорошему, тоже должно выйти читаемым текстом, но полевые испытания показали, что почему-то невсегда.

a = "Привет".decode('utf-8')

print a


Посути, мы ввели текст в utf-8 и декодировали его в unicode, Т.Е. Практически повторили предыдущий эксперимент, но тут мы дали строку в utf-8 и явно декодировали ее в unicode.

a = u"Привет".encode('utf-8')

print a


Тут мы создали юникодовый литерал и явно преобразовали его в utf-8. Скорее всего, русский текст будет выводиться хорошо и без юникодового литерала и разных там кодирований/декодирований. Но также юникодовый литерал, должен правильно выводиться при использовании оператора print.


Еще один способ создания юникодового литерала, заключается в использовании функции преобразования типов unicode(), где первым аргументом мы передаем строчный литерал, а вторым указываем его текущую кодировку. На выходе получим юникодовый литерал:

>>> a = unicode('Привет', 'cp866')

>>> a

u'\u041f\u0440\u0438\u0432\u0435\u0442'

>>> print a

Привет

>>> type(a)



>>>



Методы кодирования/декодирования



Для объекта строки и юникода, есть существуют методы, позволяющие кодировать и декодировать данные. Метод decode() для строчного литерала, позволит декодировать его из текущей кодировки в юникод, создав тем самым объект unicode.

>>> a

'\xaf\xe0\xa8\xa2\xa5\xe2'

>>> type(a)



>>> a = a.decode('cp866')

>>> a

u'\u043f\u0440\u0438\u0432\u0435\u0442'

>>> type(a)



>>>


Метод encode(), позволяет закодировать юникодовый литерал в строчный, явно указав необходимую кодировку.

>>> a = u'Привет'

>>> a

u'\u041f\u0440\u0438\u0432\u0435\u0442'

>>> type(a)



>>> a = a.encode('cp866')

>>> a

'\x8f\xe0\xa8\xa2\xa5\xe2'

>>> type(a)



>>>



Подведем итоги



Мы неможем прочесть текст, закодированный в одной кодировке при помощи другой. Мы пытаемся все держать в юникоде, чтобы проще было ориентироваться в том, что происходит в рамках нашего кода, Т.Е. Все входящие данные, мы преобразовываем в юникодовый литерал, а при необходимости вернуть эти данные куда-то, мы явно кодируем их в необходимую кодировку. Если вы попытаетесь заставить взаимодействовать между собой данные в разных кодировках или данные закодированные в какой-то кодировке и данные в юникоде, в лучшем случае выпадет трекбек, в худшем трекбека небудет, но и работать тоже ничего небудет.

Комментариев нет:

Отправить комментарий