Посібник для Unicode в Python, зручний для початківців

Одного разу я провів пару неприємних днів на роботі, навчаючись, як правильно поводитися з рядками Unicode в Python. За ці два дні я з’їв багато закусок - приблизно одну торбинку золотих рибок за одну з цих помилок, що мало б бути занадто звичним для тих, хто програмує на Python:

UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xf0 in position 0: ordinal not in range(128)

Вирішуючи своє питання, я багато гуглював, що вказало мені на кілька необхідних статей. Але якими б вони не були, усі вони були написані без допомоги найважливішого аспекту спілкування в сучасні дні.

Тобто: всі вони були написані без допомоги смайликів.

Тож, щоб скористатися цією ситуацією, я вирішив написати власний посібник із розуміння Unicode з великою кількістю облич та піктограм, відтворених на цьому шляху?

Перш ніж заглибитися в технічні деталі, давайте почнемо із цікавого питання. Які ваші улюблені смайлики?

Моє - це «обличчя з відкритим ротом», яке виглядає так? - з одним із головних застережень. Те, що ви бачите, насправді залежить від платформи, яку ви використовуєте для читання цієї публікації!

Переглядаючи на моєму Mac, емодзі виглядає як жовта куля для боулінгу. На моєму планшеті Samsung очі чорні та круглі, підкреслені білою крапкою, яка видає більшу глибину емоцій.

Скопіюйте та вставте смайлик (?) У Twitter, і ви побачите щось зовсім інше. Однак скопіюйте та вставте його у messenger.com, і ви побачите, чому це мій улюблений.

???? Чому вони всі різні?

Примітка: Станом на 9 липня 2018 року: Messenger, схоже, оновив свої піктограми смайлів, тому піктограма у верхньому правому куті більше не застосовується. ?

Ця весела маленька таємниця - це наша сега у світ Unicode, оскільки смайлики є частиною стандарту Unicode з 2010 року. Окрім того, що нам надають смайлики, Unicode є важливим, оскільки це найкращий вибір Інтернету для послідовного “кодування, представлення та обробка тексту ”.

Unicode & Encoding: Короткий посібник

Як і в багатьох темах, найкращий спосіб зрозуміти Unicode - це знати контекст, що оточує його створення - і для цього потрібно прочитати статтю Джоеля Спольського.

Код Точки

Оскільки ми зараз увійшли у світ Unicode, нам потрібно спочатку відокремити смайли від чудово виразних значків, якими вони є, і пов’язати їх з чимось набагато менш захоплюючим. Отже, замість того, щоб думати про емодзі з точки зору речей або емоцій, які вони представляють, ми замість цього будемо думати про кожен смайлик як просте число. Цей номер відомий як кодова точка .

Кодові точки - це ключова концепція Unicode, яка “була розроблена для підтримки всесвітнього обміну, обробки та відображення письмових текстів різноманітними мовами ... сучасного світу”. Він робить це, пов’язуючи практично кожен символ для друку з унікальною кодовою точкою. Разом ці символи складають набір символів Unicode .

Точки коду, як правило, пишуться в шістнадцятковій формі та мають префікс, U+щоб позначити підключення до Unicode, представляючи символи з:

  • екзотичні мови, такі як телугу [ఋ | кодова точка: U + 0C0B]
  • шахові символи [♖ | кодовий пункт: U + 2656]
  • і, звичайно, смайлики [? | кодова точка: U + 1F64C]

Гліфи - це те, що ви бачите

Фактичне відображення кодових точок на екрані називається гліфами (повне відображеннякодових точок на гліфи відомий як шрифт ) .

В якості прикладу , взяти цей лист А, яке є кодової точкою U+0041в Unicode. "А", яке ви бачите своїми очима, - це гліф - це схоже на те, що відбувається, оскільки воно відображається шрифтом Medium. Якби ви змінили шрифт на, наприклад, Times New Roman, змінився б лише гліф "А" - кодова точка, що лежить в основі, ні.

Гліфи - це відповідь на нашу маленьку таємницю візуалізації. Під капюшоном усі варіанти обличчя з відкритим ротом смайликів вказують на одну і ту ж точку коду U+1F62E, але гліф, що представляє його, залежить від платформи?.

Точки коду - це абстракції

Оскільки вони нічого не говорять про те, як їх візуалізують візуально (потрібні шрифт і гліф, щоб «оживити їх»), точкові точки називаються абстракцією.

Але так само, як кодові точки є абстракцією для кінцевих користувачів, вони також є абстракцією для комп’ютерів. Це пов’язано з тим, що для кодових точок потрібне кодування символів, щоб перетворити їх в єдине, що комп’ютери можуть інтерпретувати: байти. Після перетворення в байти кодові точки можна зберігати у файлах або надсилати по мережі на інший комп'ютер? ➡️ ?.

UTF-8 в даний час є найпопулярнішим кодуванням символів у світі. UTF-8 використовує набір правил для перетворення кодової точки в унікальну послідовність (від 1 до 4) байтів, і навпаки. Кажуть, що кодові точки кодуються в послідовність байтів, а послідовності байтів декодуються в кодові точки. Цей потік переповнення стека пояснює, як працює алгоритм кодування UTF-8.

Однак, хоча UTF-8 є переважаючим кодуванням символів у світі, це далеко не єдине. Наприклад, UTF-16 - це альтернативне кодування символів набору символів Unicode. На зображенні нижче порівнюються кодування UTF-8 та UTF-16 наших смайликів.

Проблеми виникають, коли один комп'ютер кодує точки коду в байти з одним кодуванням, а інший комп'ютер (або інший процес на тому ж комп'ютері) декодує ці байти за допомогою іншого.

На щастя, UTF-8 є досить повсюдним, щоб здебільшого нам не довелося турбуватися про невідповідні кодування символів. Але коли вони трапляються, знайомство з вищезазначеними поняттями вимагає виходу з халепи.

Короткий підсумок

  • Unicode - це сукупність кодових точок , які представляють собою звичайні числа, які зазвичай пишуться в шістнадцятковій формі та мають префікс U+. Ці кодові точки відображають практично кожен символ для друку з письмових мов у цілому світі.
  • Гліфи - це фізичний прояв персонажа. Цей хлопець ? є гліфом. А П ОНТ є відображенням кодових точок в гліфи.
  • Для того, щоб надіслати їх по мережі або зберегти у файлі, символи та їх основні кодові точки повинні бути закодовані в байти. Кодування містить детальну інформацію про те , як точка - коду вбудована в електронних даних.
  • UTF-8 в даний час є популярним у світі кодуванням символів. Враховуючи код, UTF-8 кодує його в послідовність байтів. Враховуючи послідовність байтів, UTF-8 декодує її в кодову точку.

Практичний приклад

Правильний візуалізація символів Unicode передбачає обхід ланцюжка, починаючи від байтів до кодових точок і закінчуючи гліфами.

Давайте тепер скористаємось текстовим редактором, щоб побачити практичний приклад цього ланцюга, а також типи проблем, які можуть виникнути, коли щось піде не так. Текстові редактори ідеально підходять, оскільки вони включають усі три частини ланцюжка візуалізації, показаних вище.

Примітка: Наступний приклад був зроблений на моєму MacOS із використанням Sublime Text 3. І для того, щоб надати належне там, де належить кредит: початок цього прикладу сильно натхненний цим постом від Філіпа Го, який познайомив мене з hexdumpкомандою (і цілою кількістю більше).

Ми почнемо з текстового файлу, що містить одного символу - мого улюбленого смайлика «обличчя з відкритим ротом». Для тих, хто хоче продовжити, я розмістив цей файл у суті Github, яку ви отримуєте локально curl.

curl //gist.githubusercontent.com/jzhang621/d7d9eb167f25084420049cb47510c971/raw/e35f9669785d83db864f9d6b21faf03d9e51608d/emoji.txt > emoji.txt

Як ми дізналися, для того, щоб його зберегти у файл, смайли кодувались у байти за допомогою кодування символів. Цей конкретний файл був закодований за допомогою UTF-8, і ми можемо використовувати hexdumpкоманду для перевірки фактичного вмісту байтів у файлі.

j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae 0000004

The output of hexdump tells us the file contains 4 bytes total, each of which is written in hexadecimal. The actual byte sequence f0 9f 98 ae matches the expected UTF-8 encoded byte sequence, as shown below.

Now, let’s open our file in Sublime Text, where we should see our single ? character. Since we see the expected glyph, we can assume Sublime Text used the correct character encoding to decode those bytes into code points. Let’s confirm by opening up the console View -> Show Console, and inspecting the view object that Sublime Text exposes as part of its Python API.

>>> view
# returns the encoding currently associated with the file>>> view.encoding()'UTF-8'

With a bit of Python knowledge, we can also find the Unicode code point associated with our emoji:

# Returns the character at the given position>>> view.substr(0)'?' 
# ord returns an integer representing the Unicode code point of the character (docs)>>> ord(view.substr(0))128558
# convert code point to hexadecimal, and format with U+>>> print('U+%x' % ord(view.substr(0)))U+1f62e

Again, just as we expected. This illustrates a full traversal of the Unicode rendering chain, which involved:

  • reading the file as a sequence of UTF-8 encoded bytes.
  • decoding the bytes into a Unicode code point.
  • rendering the glyph associated with the code point.

So far, so good ?.

Different Bytes, Same Emoji

Aside from being my favorite text editor, I chose Sublime Text for this example because it allows for easy experimentation with character encodings.

We can now save the file using a different character encoding. To do so, click File -> Save with Encoding -> UTF-16 BE. (Very briefly, UTF-16 is an alternative character encoding of the Unicode character set. Instead of encoding the most common characters using one byte, like UTF-8, UTF-16 encodes every point from 1–65536 using two bytes. Code points greater than 65536, like our emoji, are encoded using surrogate pairs. The BE stands for Big Endian).

When we use hexdump to inspect the file again, we see that byte contents have changed.

# (before: UTF-8)j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae 0000004
# (after: UTF-16 BE)j|encoding: hexdump emoji.txt0000000 d8 3d de 2e0000004

Back in Sublime Text, we still see the same ? character staring at us. Saving the file with a different character encoding might have changed the actual contents of the file, but it also updated Sublime Text’s internal representation of how to interpret those bytes. We can confirm by firing up the console again.

>>> view.encoding()'UTF-16 BE'

From here on up, everything else is the same.

>>> view.substr(0)'?' 
>>> ord(view.substr(0))128558
>>> print('U+%x' % ord(view.substr(0)))U+1f62e

The bytes may have changed, but the code point did not — and the emoji remains the same.

Same Bytes, But What The đŸ˜®

Time for some encoding “fun”. First, let’s re-encode our file using UTF-8, because it makes for a better example.

Let’s now go ahead use Sublime Text to re-open an existing file using a different character encoding. Under File -> Reopen with Encoding, click Vietnamese (Windows 1258), which turns our emoji character into the following four nonsensical characters: đŸ˜®.

When we click “Reopen with Encoding”, we aren’t changing the actual byte contents of the file, but rather, the way Sublime Text interprets those bytes. Hexdump confirms the bytes are the same:

j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae0000004

To understand why we see these nonsensical characters, we need to consult the Windows-1258 code page, which is a mapping of bytes to a Vietnamese language character set. (Think of a code page as the table produced by a character encoding). As this code page contains a character set with less than 255 characters, each character’s code points can be expressed as a decimal number between 0 and 255, which in turn can all be encoded using 1 byte.

Because our single ? emoji requires 4 bytes to encode using UTF-8, we now see 4 characters when we interpret the file with the Windows-1258 encoding.

A wrong choice of character encoding has a direct impact on what we can see and comprehend by garbling characters into an incomprehensible mess.

Now, onto the “fun” part, which I include to add some color to Unicode and why it exists. Before Unicode, there were many different code pages such as Windows-1258 in existence, each with a different way of mapping 1 byte’s worth of data into 255 characters. Unicode was created in order to incorporate all the different characters of the all the different code pages into one system. In other words, Unicode is a superset of Windows-1258, and each character in the Windows-1258 code page has a Unicode counterpart.

In fact, these Unicode counterparts are what allows Sublime Text to convert between different character encodings with a click of a button. Internally, Sublime Text still represents each of our “Windows-1258 decoded” characters as a Unicode code point, as we see below when we fire up the console:

>>> view.encoding()'Vietnamese (Windows 1258)'
# Python 3 strings are "immutable sequences of Unicode code points">>> type(view.substr(0))
>>> view.substr(0)'đ'>>> view.substr(1)'Ÿ'>>> view.substr(2)'˜'>>> view.substr(3)'®'
>>> ['U+%04x' % ord(view.substr(x)) for x in range(0, 4)]['U+0111', 'U+0178', 'U+02dc', 'U+00ae']

This means that we can re-save our 4 nonsensical characters using UTF-8. I’ll leave this one up to you — if you do so, and can correctly predict the resulting hexdump of the file, then you’ve successfully understood the key concepts behind Unicode, code points, and character encodings. (Use this UTF-8 code page. Answer can be found at the very end of this article. ).

Wrapping up

Working effectively with Unicode involves always knowing what level of the rendering chain you are operating on. It means always asking yourself: what do I have? Under the hood, glyphs are nothing but code points. If you are working with code points, know that those code points must be encoded into bytes with a character encoding. If you have a sequence of bytes representing text, know that those bytes are meaningless without knowing the character encoding that was used create those bytes.

As with any computer science topic, the best way to learn about Unicode is to experiment. Enter characters, play with character encodings, and make predictions that you verify using hexdump. While I hope this article explains everything you need to know about Unicode, I will be more than happy if it merely sets you up to run your own experiments.

Thanks for reading! ?

Answer:

j|encoding: $ hexdump emoji.txt0000000 c4 91 c5 b8 cb 9c c2 ae0000008