Python str против типов юникода

103

Работая с Python 2.7, мне интересно, какое реальное преимущество дает использование типа unicodeвместо str, поскольку оба они, похоже, могут содержать строки Unicode. Есть ли какая-то особая причина, кроме возможности устанавливать коды Unicode в unicodeстроках с помощью escape-символа \?:

Выполнение модуля с помощью:

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

a = 'á'
ua = u'á'
print a, ua

Результаты в: á, á

РЕДАКТИРОВАТЬ:

Дополнительное тестирование с использованием оболочки Python:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Итак, unicodeкажется , что строка кодируется с использованием latin1вместо, utf-8а необработанная строка кодируется с помощью utf-8? Я запуталась еще больше! : S

Caumons
источник
Нет кодировки для unicode, это просто абстракция символа Юникода; unicodeможно преобразовать в strнекоторую кодировку (например utf-8).
Бин

Ответы:

179

unicodeпредназначен для обработки текста . Текст - это последовательность кодовых точек, размер которых может превышать один байт . Текст может быть закодирован в определенной кодировке для представления текста в виде необработанных байтов (например utf-8, latin-1...).

Обратите внимание, что unicode не кодируется ! Внутреннее представление, используемое python, является деталью реализации, и вы не должны беспокоиться об этом, если оно может представлять нужные вам точки кода.

Напротив, strв Python 2 есть простая последовательность байтов . Это не текст!

Вы можете думать об этом unicodeкак об общем представлении некоторого текста, который можно разными способами закодировать в последовательность двоичных данных, представленных через str.

Примечание. В Python 3 он unicodeбыл переименован в strи появился новый bytesтип для простой последовательности байтов.

Некоторые различия, которые вы можете увидеть:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

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

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

То, что раньше было действительным UTF-8, больше не существует. Используя строку Unicode, вы не можете работать таким образом, чтобы результирующая строка не была допустимым текстом Unicode. Вы можете удалить кодовую точку, заменить кодовую точку другой кодовой точкой и т. Д., Но вы не можете вмешиваться во внутреннее представление.

Бакуриу
источник
4
Большое спасибо за ваш ответ, это очень помогло! Самая проясняющая часть для меня: «Юникод не кодируется! Внутреннее представление, используемое python, является деталью реализации, и вы не должны беспокоиться об этом [...]». Итак, при сериализации unicodeобъектов, я думаю, мы сначала должны явно указать encode()им правильный формат кодирования, поскольку мы не знаем, какой из них используется внутри для представления unicodeзначения.
Caumons
10
Да. Если вы хотите сохранить некоторый текст (например, в файл), вы должны представить его байтами, т.е. вы должны его закодировать . При извлечении контента вы должны знать используемую кодировку, чтобы иметь возможность декодировать байты в unicodeобъект.
Bakuriu 03
Извините, но утверждение, unicodeкоторое не закодировано, явно неверно. UTF-16 / UCS-2 и UTF-32 / UCS-4 также являются кодировками ... и в будущем, возможно, их будет создано больше. Дело в том, что то, что вы не должны заботиться о деталях реализации (и, действительно, не должны!), Это еще не означает, что unicodeэто не закодировано. Конечно, есть. Может ли это быть .decode()- совсем другая история.
0xC0000022L
1
@ 0xC0000022L Может быть, предложение как то непонятно. Надо сказать: unicodeвнутреннее представление объекта может быть любым, в том числе нестандартным. В частности , в Python3 + unicode действительно использовать нестандартное внутреннее представление , что также изменяется в зависимости от данных , содержащееся. Таким образом, это не стандартная кодировка . Unicode как текстовый стандарт определяет только кодовые точки, которые являются абстрактным представлением текста, существует множество способов кодирования Unicode в памяти, включая стандартный utf-X и т. Д. Python использует свой собственный способ для повышения эффективности.
Bakuriu
1
@ 0xC0000022L Также тот факт, что UTF-16 является кодировкой, не имеет ничего общего с unicodeобъектом CPython , поскольку он не использует ни UTF-16, ни UTF-32. Он использует специальное представление, и если вы хотите закодировать данные в фактические байты, вы должны использовать encode. Также: язык не определяет, как unicodeреализован, поэтому разные версии или реализации python могут (и имеют ) разное внутреннее представление.
Bakuriu
38

Юникод и кодировки - совершенно разные, не связанные между собой вещи.

Unicode

Присваивает числовой идентификатор каждому символу:

  • 0x41 → А
  • 0xE1 → á
  • 0x414 → Д

Итак, Unicode присваивает номер 0x41 для A, 0xE1 для á и 0x414 для Д.

Даже маленькая стрелка →, которую я использовал, имеет свой номер в Юникоде, это 0x2192. И даже у смайликов есть номера в Юникоде, 😂 - это 0x1F602.

Вы можете посмотреть номера Unicode всех символов в этой таблице . В частности, вы можете найти первые три символа выше здесь , стрелка здесь , и смайликов здесь .

Эти номера, присвоенные всем символам Unicode, называются кодовыми точками .

Цель всего этого - предоставить средства для однозначной ссылки на каждый символ. Например, если я говорю о 😂, вместо того, чтобы сказать «ну вы знаете, этот смеющийся смайлик со слезами» , я могу просто сказать кодовая точка Unicode 0x1F602 . Легче, правда?

Обратите внимание, что кодовые точки Unicode обычно форматируются в начале U+, а затем шестнадцатеричное числовое значение дополняется как минимум до 4 цифр. Итак, приведенные выше примеры будут: U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Кодовые точки Unicode варьируются от U + 0000 до U + 10FFFF. Это 1114 112 номеров. 2048 из этих номеров используются для суррогатов , таким образом, осталось 1 112 064. Это означает, что Unicode может назначить уникальный идентификатор (кодовую точку) для 1112 064 различных символов. Еще не все эти кодовые точки присвоены символу, а Unicode постоянно расширяется (например, когда вводятся новые смайлы).

Важно помнить, что все, что делает Unicode, - это присваивает каждому символу числовой идентификатор, называемый кодовой точкой, для простой и однозначной ссылки.

Кодировки

Сопоставьте символы с битовыми шаблонами.

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

Существует множество различных кодировок, охватывающих разные подмножества символов. В англоязычном мире наиболее распространены следующие кодировки:

ASCII

Преобразует 128 символов (кодовые точки U + 0000 в U + 007F) в битовые комбинации длиной 7.

Пример:

  • а → 1100001 (0x61)

Вы можете увидеть все сопоставления в этой таблице .

ISO 8859-1 (также известный как Latin-1)

Отображает 191 символ (кодовые точки от U + 0020 до U + 007E и от U + 00A0 до U + 00FF) в битовые комбинации длиной 8.

Пример:

  • а → 01100001 (0x61)
  • á → 11100001 (0xE1)

Вы можете увидеть все сопоставления в этой таблице .

UTF-8

Карты 1,112,064 символов (все существующее Unicode точки коды) битовые паттерны либо длина 8, 16, 24 или 32 бит (то есть, 1, 2, 3 или 4 байта).

Пример:

  • а → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

Кстати UTF-8 кодирует символы в битовые строки очень хорошо описано здесь .

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

Глядя на приведенные выше примеры, становится ясно, насколько полезен Unicode.

Например, если я Latin-1 и хочу объяснить свою кодировку á, мне не нужно говорить:

«Я кодирую это a с помощью aigu (или как вы называете эту восходящую полосу) как 11100001»

Но я могу просто сказать:

"Я кодирую U + 00E1 как 11100001"

И если я UTF-8 , я могу сказать:

«Я, в свою очередь, кодирую U + 00E1 как 11000011 10100001»

И всем однозначно ясно, о каком персонаже идет речь.

Теперь к часто возникающей путанице

Верно, что иногда битовый шаблон кодировки, если вы интерпретируете его как двоичное число, совпадает с кодовой точкой Unicode этого символа.

Например:

  • ASCII кодирует a как 1100001, что можно интерпретировать как шестнадцатеричное число 0x61 , а кодовая точка Unicode a - U + 0061 .
  • Latin-1 кодирует á как 11100001, что можно интерпретировать как шестнадцатеричное число 0xE1 , а кодовая точка Unicode для á - U + 00E1 .

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

Никто даже не говорит, что битовую строку типа 11100001 нужно интерпретировать как двоичное число. Просто посмотрите на это как на последовательность битов, которые Latin-1 использует для кодирования символа á .

Вернуться к вашему вопросу

Ваш интерпретатор Python использует кодировку UTF-8 .

Вот что происходит в ваших примерах:

Пример 1

Следующее кодирует символ á в UTF-8. В результате получается битовая строка 11000011 10100001, которая сохраняется в переменной a.

>>> a = 'á'

Когда вы смотрите на значение a, его содержимое 11000011 10100001 форматируется как шестнадцатеричное число 0xC3 0xA1 и выводится как '\xc3\xa1':

>>> a
'\xc3\xa1'

Пример 2

Следующее сохраняет кодовую точку Unicode для á, которая является U + 00E1, в переменной ua(мы не знаем, какой формат данных Python использует внутри для представления кодовой точки U + 00E1 в памяти, и это неважно для нас):

>>> ua = u'á'

Когда вы смотрите на значение ua, Python сообщает вам, что оно содержит кодовую точку U + 00E1:

>>> ua
u'\xe1'

Пример 3

Следующее кодирует кодовую точку Unicode U + 00E1 (представляющую символ á) с помощью UTF-8, что приводит к битовому шаблону 11000011 10100001. Опять же, для вывода этот битовый шаблон представлен как шестнадцатеричное число 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Пример 4

Следующее кодирует кодовую точку Unicode U + 00E1 (представляющую символ á) с помощью Latin-1, что приводит к битовому шаблону 11100001. Для вывода этот битовый шаблон представлен как шестнадцатеричное число 0xE1, которое по совпадению совпадает с исходным кодовая точка U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Нет связи между объектом Unicode uaи кодировкой Latin-1. То, что кодовая точка á - U + 00E1, а кодировка Latin-1 для á - 0xE1 (если вы интерпретируете битовый шаблон кодирования как двоичное число), является чистым совпадением.

Weibeld
источник
31

Ваш терминал настроен на UTF-8.

То, что полиграфия aработает - случайность; вы записываете на терминал необработанные байты UTF-8. aпредставляет собой значение длины два , содержащее два байта, шестнадцатеричные значения C3 и A1, а uaпредставляет собой значение Unicode длины один , содержащее кодовую точку U + 00E1.

Эта разница в длине - одна из основных причин использования значений Unicode; вы не можете легко измерить количество текстовых символов в байтовой строке; len()строки байт говорят вам , как были использованы много байт, а не как было закодированы много символов.

Вы можете увидеть разницу , когда вы закодировать значение Юникода для различных выходных кодировок:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Обратите внимание, что первые 256 кодовых точек стандарта Unicode соответствуют стандарту Latin 1, поэтому кодовая точка U + 00E1 кодируется в Latin 1 как байт с шестнадцатеричным значением E1.

Кроме того, Python использует escape-коды в представлениях как юникодовых, так и байтовых строк, а младшие кодовые точки, которые не печатаются ASCII, также представлены с помощью \x..escape-значений. Вот почему строка Unicode с кодовой точкой от 128 до 255 выглядит так же, как кодировка Latin 1. Если у вас есть строка Unicode с кодовыми точками за пределами U + 00FF, \u....вместо нее используется другая escape-последовательность с четырехзначным шестнадцатеричным значением.

Похоже, вы еще не до конца понимаете, в чем разница между Unicode и кодировкой. Пожалуйста, прочтите следующие статьи, прежде чем продолжить:

Мартейн Питерс
источник
Я отредактировал свой вопрос с дальнейшим тестированием. Я какое-то время читал о юникоде и различных кодировках, и я думаю, что понимаю теорию, но при фактическом тестировании кода Python я не улавливаю, что происходит
Caumons
1
Кодировка latin-1 соответствует первым 256 кодовым точкам стандарта Unicode. Вот почему U + 00E1 кодируется на \xe1латинице 1.
Мартин Питерс
2
Это самый важный аспект Unicode. Это не кодировка . Это текст. Unicode - это стандарт, который включает в себя гораздо больше, например информацию о том, какие кодовые точки являются числами, пробелами или другими категориями, которые должны отображаться слева направо или справа налево и т. Д. И т. Д. И т. Д. И т. Д.
Мартейн Питерс
1
Это все равно, что сказать, что Unicode похож на «интерфейс», а Encoding - на реальную «реализацию».
Caumons 03
2
@Varun: вы должны использовать узкую сборку Python 2, которая использует UCS-2 внутренне и искажает что-либо, превышающее U + FFFF, как имеющее длину два. Python 3 и сборка UCS-2 (широкая) покажут вам, что длина действительно равна 1.
Мартин Питерс
2

Когда вы определяете a как unicode, символы a и á равны. В противном случае á считается как два символа. Попробуйте len (a) и len (au). В дополнение к этому вам может потребоваться кодировка при работе с другими средами. Например, если вы используете md5, вы получите разные значения для a и ua

Али Расим Кокал
источник