Подтвердить что ты не робот

Python 3: Демистификация методов кодирования и декодирования

Скажем, у меня есть строка в Python:

>>> s = 'python'
>>> len(s)
6

Теперь я encode эта строка выглядит так:

>>> b = s.encode('utf-8')
>>> b16 = s.encode('utf-16')
>>> b32 = s.encode('utf-32')

То, что я получаю из вышеперечисленных операций, - это массив байтов, то есть b, b16 и b32 - это просто массивы байтов (каждый байт, конечно, 8-битный).

Но мы закодировали строку. Итак, что это значит? Как мы присоединяем понятие "кодирования" к необработанному массиву байтов?

Ответ заключается в том, что каждый из этих массивов байтов генерируется определенным образом. Посмотрите на эти массивы:

>>> [hex(x) for x in b]
['0x70', '0x79', '0x74', '0x68', '0x6f', '0x6e']

>>> len(b)
6

Этот массив указывает, что для каждого символа мы имеем один байт (потому что все символы падают ниже 127). Следовательно, можно сказать, что "кодирование" строки в "utf-8" собирает каждый символ, соответствующий кодовой точке, и помещает его в массив. Если точка кода не может поместиться в один байт, то utf-8 потребляет два байта. Следовательно, utf-8 потребляет наименьшее количество байтов.

>>> [hex(x) for x in b16]
['0xff', '0xfe', '0x70', '0x0', '0x79', '0x0', '0x74', '0x0', '0x68', '0x0', '0x6f', '0x0', '0x6e',  '0x0']

>>> len(b16)
14     # (2 + 6*2)

Здесь мы видим, что "encoding to utf-16" сначала помещает двухбайтную BOM (FF FE) в массив байтов, а затем для каждого символа он помещает два байта в массив. (В нашем случае второй байт всегда равен нулю)

>>> [hex(x) for x in b32]
['0xff', '0xfe', '0x0', '0x0', '0x70', '0x0', '0x0', '0x0', '0x79', '0x0', '0x0', '0x0', '0x74', '0x0', '0x0', '0x0', '0x68', '0x0', '0x0', '0x0', '0x6f', '0x0', '0x0', '0x0', '0x6e', '0x0', '0x0', '0x0']

>>> len(b32)
28     # (2+ 6*4 + 2)

В случае "кодирования в utf-32" мы сначала помещаем спецификацию, затем для каждого символа мы помещаем четыре байта и, наконец, ставим два байта в массив.

Следовательно, можно сказать, что "процесс кодирования" собирает 1 2 или 4 байта (в зависимости от имени кодировки) для каждого символа в строке и добавляет и добавляет к ним больше байтов для создания конечного массива результатов байтов.

Теперь, мои вопросы:

  • Насколько я понимаю процесс кодирования или я что-то упускаю?
  • Мы видим, что представление памяти переменных b, b16 и b32 на самом деле является списком байтов. Каково представление памяти в строке? Точно что хранится в памяти для строки?
  • Мы знаем, что когда мы делаем encode(), каждая символьная соответствующая кодовая точка собирается (кодовая точка, соответствующая имени кодировки) и помещается в массив или байты. Что именно происходит, когда мы делаем decode()?
  • Мы можем видеть, что в utf-16 и utf-32 добавлена ​​спецификация, но почему в кодировке utf-32 добавляются два нулевых байта?
4b9b3361

Ответ 1

Прежде всего, UTF-32 является 4-байтовой кодировкой, поэтому его спецификация также представляет собой четырехбайтную последовательность:

>>> import codecs
>>> codecs.BOM_UTF32
b'\xff\xfe\x00\x00'

И поскольку разные компьютерные архитектуры обрабатывают байтовые порядки по-разному (называемые Endianess), есть два варианта спецификации: маленький и большой endian:

>>> codecs.BOM_UTF32_LE
b'\xff\xfe\x00\x00'
>>> codecs.BOM_UTF32_BE
b'\x00\x00\xfe\xff'

Цель спецификации - передать этот порядок декодеру; прочитайте спецификацию, и вы знаете, если она большая или маленькая. Итак, последние два нулевых байта в вашей строке UTF-32 являются частью последнего закодированного символа.

UTF-16 Таким образом, спецификация аналогична, поскольку существует два варианта:

>>> codecs.BOM_UTF16
b'\xff\xfe'
>>> codecs.BOM_UTF16_LE
b'\xff\xfe'
>>> codecs.BOM_UTF16_BE
b'\xfe\xff'

Это зависит от вашей компьютерной архитектуры, которая используется по умолчанию.

UTF-8 вообще не нуждается в спецификации; UTF-8 использует 1 или более байт на символ (добавление байтов, необходимых для кодирования более сложных значений), но порядок этих байтов определен в стандарте. Microsoft сочла необходимым в любом случае ввести спецификацию UTF-8 (поэтому приложение "Блокнот" может обнаружить UTF-8), но поскольку порядок спецификации не меняется, его использование не рекомендуется.

Что касается того, что хранится в Python для строк unicode; что фактически изменилось в Python 3.3. До 3.3, внутренне на уровне C, Python либо сохранял комбинации байтов UTF16, либо UTF32, в зависимости от того, был ли Python скомпилирован с поддержкой широкого символа (см. Как узнать, скомпилирован ли Python с UCS-2 или UCS-4?, UCS-2 по существу UTF-16, а UCS-4 - UTF-32). Таким образом, каждый символ занимает 2 или 4 байта памяти.

Начиная с Python 3.3, внутреннее представление использует минимальное количество байтов, необходимых для представления всех символов в строке. Для обычного ASCII и латинского кодируемого текста используется 1 байт, для остальных BMP используются 2 байта, а текст, содержащий символы за пределами этого Используются 4 байта. Python переключается между форматами по мере необходимости. Таким образом, в большинстве случаев хранилище стало намного более эффективным. Более подробно см. Что нового в Python 3.3.

Я могу настоятельно рекомендовать вам читать Unicode и Python с помощью:

Ответ 2

  • Ваше понимание по существу правильное, насколько это возможно, хотя это не действительно "1, 2 или 4 байта". Для UTF-32 это будет 4 байта. Для UTF-16 и UTF-8 количество байтов зависит от кодируемого символа. Для UTF-16 это будет либо 2, либо 4 байта. Для UTF-8 это может быть 1, 2, 3 или 4 байта. Но да, в основном кодирование принимает точку кода юникода и сопоставляет ее с последовательностью байтов. Как это сопоставление выполняется, зависит от кодировки. Для UTF-32 это просто прямое шестнадцатеричное представление номера кодовой точки. Для UTF-16 это обычно так, но будет немного отличаться для необычных символов (вне базовой многоязычной плоскости). Для UTF-8 кодировка более сложна (см. Wikipedia.) Что касается дополнительных байтов в начале, это маркеры байтов которые определяют, в каком порядке фрагменты кодовой точки поступают в UTF-16 или UTF-32.
  • Я думаю, вы могли бы посмотреть на внутренности, но точка типа строки (или тип Unicode в Python 2) должна защитить вас от этой информации, точно так же, как точка списка Python - защитить вас от необходимости манипулировать необработанной структурой памяти этого списка. Строковый тип данных существует, поэтому вы можете работать с кодами Unicode, не беспокоясь о представлении памяти. Если вы хотите работать с необработанными байтами, закодируйте строку.
  • Когда вы выполняете декодирование, он в основном сканирует строку, ища куски байтов. Схемы кодирования по существу обеспечивают "подсказки", которые позволяют декодеру видеть, когда один символ заканчивается, а другой начинается. Таким образом, декодер просматривает и использует эти подсказки, чтобы найти границы между символами, затем просматривает каждую часть, чтобы увидеть, какой символ она представляет в этой кодировке. Вы можете просмотреть отдельные кодировки в Википедии или тому подобное, если хотите просмотреть сведения о том, как каждый код кода кодирования указывает туда и обратно с байтами.
  • Два нулевых байта являются частью маркера байтового байта для UTF-32. Поскольку UTF-32 всегда использует 4 байта на кодовую точку, спецификация также имеет четыре байта. В основном маркер FFFE, который вы видите в UTF-16, имеет нулевое дополнение с двумя дополнительными нулевыми байтами. Эти маркеры байтового байта указывают, соответствуют ли числа, составляющие кодовую точку, от самого большого до наименьшего или наименьшего к наибольшему. В основном это похоже на выбор того, следует ли записывать число "одна тысяча двести тридцать четыре" как 1234 или 4321. Различные компьютерные архитектуры делают разные варианты в этом вопросе.

Ответ 3

Я предполагаю, что вы используете Python 3 (в Python 2 "строка" на самом деле представляет собой массив байтов, что вызывает боль в Unicode).

Строка

A (Unicode) представляет собой концепцию последовательности кодовых точек Unicode, которые являются абстрактными объектами, соответствующими "символам". Вы можете увидеть фактическую реализацию С++ в репозитории Python. Поскольку компьютеры не имеют неотъемлемой концепции кодовой точки, "кодировка" указывает частичную биекцию между кодовыми точками и байтовыми последовательностями.

Кодировки настроены так, что в кодировке переменной ширины нет двусмысленности - если вы видите байт, вы всегда знаете, завершает ли он текущую кодовую точку или нужно ли читать другую. Технически это называется без префикса. Итак, когда вы делаете .decode(), Python ходит по байтовому массиву, создавая закодированные символы на одном времени и вывода их.

Два нулевых байта являются частью спецификации utf32: big-endian UTF32 имеет 0x0 0x0 0xff 0xfe.