מדריך ידידותי למתחילים ל- Unicode בפייתון

פעם ביליתי כמה ימים מתסכלים בעבודה ולמדתי כיצד להתמודד נכון עם מיתרי Unicode בפייתון. במהלך היומיים האלה אכלתי הרבה חטיפים - בערך שקית אחת של דג זהב לכל אחת מהשגיאות הללו שנתקלו בהן, שאמורות להיות מוכרות מדי למי שמתכנת עם Python:

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

תוך כדי פיתרון הגיליון שלי עשיתי הרבה גוגל, מה שהצביע על כמה מאמרים הכרחיים. אך ככל שהם גדולים, כולם נכתבו ללא עזרה של היבט מכריע בתקשורת בימינו.

כלומר: כולם נכתבו ללא עזרת אימוג'י.

לכן, על מנת לנצל את המצב הזה, החלטתי לכתוב מדריך משלי להבנת Unicode, עם הרבה פרצופים וסמלים המוצגים לאורך הדרך? ✌ ?.

לפני שנצלול לפרטים טכניים, נתחיל בשאלה מהנה. מה האימוג'י האהוב עליך?

שלי הם "הפנים בפה פעור", שנראים ככה? - עם אזהרה אחת גדולה. מה שאתה רואה תלוי בפלטפורמה בה אתה משתמש לקריאת הפוסט הזה!

נצפה ב- Mac שלי, האימוג'י נראה כמו כדור באולינג צהוב. בטאבלט של סמסונג שלי העיניים שחורות ומעגליות, מודגשות על ידי נקודה לבנה המסגירה עומק גדול יותר של רגש.

העתק והדבק את האימוג'י (?) בטוויטר ותראה משהו אחר לגמרי. העתק והדבק אותו ב- messenger.com, עם זאת, ותראה מדוע הוא המועדף עלי.

???? מדוע כולם שונים?

הערה: החל מה -9 ביולי 2018: נראה כי מסנג'ר עדכן את סמלי האימוג'י שלהם, כך שהסמל בפינה השמאלית העליונה כבר לא חל. ?

התעלומה הקטנה והכיפית הזו היא המפגש שלנו עם עולם ה- Unicode, מכיוון שאימוג'ים היו חלק מתקן ה- Unicode מאז 2010. מלבד מתן האימוג'י שלנו, Unicode חשוב מכיוון שזו הבחירה המועדפת על האינטרנט ל"קידוד, ייצוג ו טיפול בטקסט ".

יוניקוד וקידוד: פריימר קצר

כמו בנושאים רבים, הדרך הטובה ביותר להבין את יוניקוד היא לדעת את ההקשר סביב יצירתו - ולשם כך נדרש קריאה במאמרו של ג'ואל ספולסקי.

נקודות קוד

מכיוון שנכנסנו עכשיו לעולם של יוניקוד, עלינו להנתק תחילה את האימוג'ים מהסמלים המביעים להפליא שהם, ולקשר אותם למשהו הרבה פחות מרגש. אז במקום לחשוב על אימוג'ים במונחים של הדברים או הרגשות שהם מייצגים, במקום זאת נחשוב על כל אימוג'י כמספר פשוט. מספר זה ידוע כנקודת קוד .

נקודות קוד הן המושג המרכזי של יוניקוד, אשר נועד "לתמוך בהחלפה, עיבוד ותצוגה עולמית של הטקסטים הכתובים של השפות המגוונות ... של העולם המודרני." זה עושה זאת על ידי שיוך כמעט כל תו להדפסה עם נקודת קוד ייחודית. יחד, תווים אלה כוללים את ערכת התווים של Unicode .

נקודות קוד נכתבות בדרך כלל בהקסדצימלי ומקודמות עם U+כדי לציין את החיבור ל- Unicode, המייצגות תווים מ:

  • שפות אקזוטיות כגון טלוגו [ఋ | נקודת קוד: U + 0C0B]
  • סמלי שחמט [♖ | נקודת קוד: U + 2656]
  • וכמובן, אימוג'ים [? | נקודת קוד: U + 1F64C]

גליפים הם מה שאתה רואה

הייצוג הממשי על המסך של נקודות קוד נקרא גליפים , (מיפוי מלאשל נקודות קוד לגליפים מכונה גופן ) .

כדוגמה , קח את האות A, שהיא נקודת קוד U+0041ב- Unicode. ה- "A" שאתה רואה בעיניים הוא גליף - זה נראה כמו שהוא נראה כי הוא מעובד עם הגופן של Medium. אם היית משנה את הגופן ל- Times New Roman למשל, רק הגליף של "A" ישתנה - נקודת הקוד הבסיסית לא הייתה.

גליפים הם התשובה לתעלומת העיבוד הקטנה שלנו. מתחת למכסה המנוע, כל וריאציות הפנים עם אימוג'י בפה פתוח מצביעות על אותה נקודת קוד U+1F62E, אך הגליף המייצג אותו משתנה לפי פלטפורמה?.

נקודות קוד הן הפשטות

מכיוון שהם לא אומרים דבר על אופן העיבוד שלהם באופן חזותי (הדורש גופן וגליף כדי "להחיותם"), נקודות קוד אמורות להיות הפשטה.

אך כמו שנקודות קוד הן הפשטה עבור משתמשי הקצה, הן גם הפשטות למחשבים. הסיבה לכך היא שנקודות קוד דורשות קידוד תווים כדי להמיר אותן לדבר היחיד שמחשבים יכולים לפרש: בתים. לאחר ההמרה לבתים, ניתן לשמור נקודות קוד בקבצים או לשלוח אותם ברשת למחשב אחר? ➡️ ?.

UTF-8 הוא כיום קידוד הדמויות הפופולרי ביותר בעולם. UTF-8 משתמש במערכת כללים להמרת נקודת קוד לרצף ייחודי של (1 עד 4) בתים, ולהיפך. נאמר כי נקודות קוד מקודדות לרצף בתים, ורצפי בתים מפוענחים לנקודות קוד. פוסט זה של Stack Overflow מסביר כיצד פועל אלגוריתם הקידוד של UTF-8.

עם זאת, למרות ש- UTF-8 הוא הדמות השולטת בעולם, הוא רחוק מלהיות היחיד. לדוגמא, UTF-16 הוא קידוד תווים חלופי של ערכת התווים Unicode. התמונה למטה משווה את קידודי UTF-8 ו- UTF-16 של האימוג'י שלנו?.

בעיות מתרחשות כאשר מחשב אחד מקודד נקודות קוד לבתים עם קידוד אחד, ומחשב אחר (או תהליך אחר באותו מחשב) מפענח את אותם בתים עם אחר.

למרבה המזל, UTF-8 נמצא בכל מקום מספיק כדי שלרוב, אנחנו לא צריכים לדאוג לקידוד תווים לא תואם. אך כאשר הם מתרחשים, נדרשת היכרות עם המושגים שהוזכרו לעיל כדי לחלץ את עצמך מהבלגן.

סיכום קצר

  • Unicode is a collection of code points, which are plain numbers typically written in hexadecimal and prefixed with U+. These code points map to virtually every printable character from the written languages around the world.
  • Glyphs are the physical manifestation of a character. This guy ? is a glyph. A font is a mapping of code points to glyphs.
  • In order to send them across the network or save them in a file, characters and their underlying code points must be encoded into bytes. A character encoding contains the details of how a code point is embedded into a sequence of bytes.
  • UTF-8 is currently the world’s must popular character encoding. Given a code point, UTF-8 encodes it into a sequence of bytes. Given a sequence of bytes, UTF-8 decodes it into a code point.

A Practical Example

The correct rendering of Unicode characters involves traversing a chain, ranging from bytes to code points to glyphs.

Let’s now use a text editor to see a practical example of this chain — as well as the types of issues that can arise when things go awry. Text editors are perfect, because they involve all three parts of the rendering chain shown above.

Note: The following example was done on my MacOS using Sublime Text 3. And to give credit where credit is due: the beginning of this example is heavily inspired by this post from Philip Guo, which introduced me to the hexdump command (and a whole lot more).

We’ll start with a text file containing a single character — my favorite “face with open mouth” emoji. For those who want to follow along, I’ve hosted this file in a Github gist, which you get locally with curl.

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

As we learned, in order for it be saved to a file, the emoji was encoded into bytes using a character encoding. This particular file was encoded using UTF-8, and we can use the hexdump command to examine the actual byte contents of the file.

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