Ένας οδηγός φιλικός για αρχάριους για το Unicode στο Python

Πέρασα κάποτε μερικές απογοητευτικές μέρες στη δουλειά, μαθαίνοντας πώς να χειριστώ σωστά τις χορδές Unicode στην Python. Κατά τη διάρκεια αυτών των δύο ημερών, έφαγα πολλά σνακ - περίπου μια τσάντα χρυσόψαρο ανά ένα από αυτά τα σφάλματα που αντιμετώπισαν, τα οποία θα έπρεπε να είναι πολύ εξοικειωμένα με όσους προγραμματίζουν με την Python:

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

Κατά την επίλυση του προβλήματός μου, έκανα πολλά googling, τα οποία με έδειξαν σε μερικά απαραίτητα άρθρα. Όμως όσο υπέροχα είναι, όλα γράφτηκαν χωρίς τη βοήθεια μιας κρίσιμης πτυχής της επικοινωνίας στη σημερινή εποχή.

Δηλαδή: όλα γράφτηκαν χωρίς τη βοήθεια emoji.

Έτσι, για να επωφεληθώ από αυτήν την κατάσταση, αποφάσισα να γράψω τον δικό μου οδηγό για την κατανόηση του Unicode, με πολλά πρόσωπα και εικονίδια να αποδίδονται στο δρόμο; ✌ ?.

Πριν εξερευνήσετε τεχνικές λεπτομέρειες, ας ξεκινήσουμε με μια διασκεδαστική ερώτηση. Ποιο είναι το αγαπημένο σας emoji;

Το δικό μου είναι το "πρόσωπο με ανοιχτό το στόμα", που μοιάζει με αυτό; - με μια μεγάλη προειδοποίηση. Αυτό που βλέπετε εξαρτάται πραγματικά από την πλατφόρμα που χρησιμοποιείτε για να διαβάσετε αυτήν την ανάρτηση!

Προβολές στο Mac μου, το emoji μοιάζει με κίτρινη μπάλα μπόουλινγκ. Στο tablet μου Samsung, τα μάτια είναι μαύρα και κυκλικά, τονίζονται από μια άσπρη κουκκίδα που προδίδει μεγαλύτερο βάθος συναισθημάτων.

Αντιγράψτε και επικολλήστε το emoji (?) Στο Twitter και θα δείτε κάτι εντελώς διαφορετικό. Αντιγράψτε και επικολλήστε το στο messenger.com, ωστόσο, και θα δείτε γιατί είναι το αγαπημένο μου.

???? Γιατί είναι όλα διαφορετικά;

Σημείωση: Από τις 9 Ιουλίου 2018: Το Messenger φαίνεται να έχει ενημερώσει τα εικονίδια emoji τους, επομένως το εικονίδιο στην επάνω δεξιά γωνία δεν ισχύει πλέον. ;

Αυτό το διασκεδαστικό μικρό μυστήριο είναι το κομμάτι μας στον κόσμο του Unicode, καθώς τα emoji είναι μέρος του Unicode Standard από το 2010. Εκτός από το να μας δώσετε emoji, το Unicode είναι σημαντικό επειδή είναι η προτιμώμενη επιλογή του Διαδικτύου για τη συνεπή «κωδικοποίηση, αναπαράσταση και χειρισμός κειμένου ».

Unicode & Encoding: Ένα σύντομο αστάρι

Όπως με πολλά θέματα, ο καλύτερος τρόπος για να κατανοήσετε το Unicode είναι να γνωρίσετε το περιβάλλον που περιβάλλει τη δημιουργία του - και για αυτό, απαιτείται ανάγνωση του άρθρου του Joel Spolsky.

Σημεία κώδικα

Από τότε που μπήκαμε στον κόσμο του Unicode, πρέπει πρώτα να διαχωρίσουμε τα emoji από τα θαυμάσια εκφραστικά εικονίδια που είναι και να τα συνδέσουμε με κάτι πολύ λιγότερο συναρπαστικό. Έτσι, αντί να σκεφτόμαστε emoji όσον αφορά τα πράγματα ή τα συναισθήματα που αντιπροσωπεύουν, αντίθετα θα σκεφτόμαστε κάθε emoji ως απλό αριθμό. Αυτός ο αριθμός είναι γνωστός ως κωδικός .

Τα σημεία κώδικα είναι η βασική ιδέα του Unicode, η οποία «σχεδιάστηκε για να υποστηρίζει την παγκόσμια ανταλλαγή, επεξεργασία και προβολή των γραπτών κειμένων των διαφορετικών γλωσσών… του σύγχρονου κόσμου». Αυτό το κάνει συνδέοντας σχεδόν κάθε εκτυπώσιμο χαρακτήρα με ένα μοναδικό σημείο κώδικα. Μαζί, αυτοί οι χαρακτήρες περιλαμβάνουν το σύνολο χαρακτήρων Unicode .

Τα σημεία κώδικα γράφονται συνήθως σε δεκαεξαδικό και προθεματίζονται για U+να υποδηλώσουν τη σύνδεση με το Unicode, που αντιπροσωπεύει χαρακτήρες από:

  • εξωτικές γλώσσες όπως η Τελούγκου [ఋ | σημείο κωδικού: U + 0C0B]
  • σύμβολα σκακιού [♖ | σημείο κωδικού: U + 2656]
  • και, φυσικά, τα emoji [; | σημείο κωδικού: U + 1F64C]

Οι Γλύφοι είναι αυτό που βλέπετε

Η πραγματική αναπαράσταση στην οθόνη των σημείων κώδικα ονομάζεται γλύφοι , (τοπλήρης χαρτογράφησητων κωδικών σημείων σε γλύφους είναι γνωστή ως γραμματοσειρά ) .

Για παράδειγμα , πάρτε αυτό το γράμμα A, το οποίο είναι σημείο κώδικα U+0041στο Unicode. Το "A" που βλέπετε με τα μάτια σας είναι ένας γλύφος - μοιάζει με τον τρόπο που το κάνει επειδή αποδίδεται με τη γραμματοσειρά Medium. Εάν επρόκειτο να αλλάξετε τη γραμματοσειρά σε, Times New Roman, για παράδειγμα, μόνο το γλύφο του "A" θα άλλαζε - το υποκείμενο σημείο κώδικα δεν θα άλλαζε.

Οι Γλύφοι είναι η απάντηση στο μικρό μας μυστήριο απόδοσης. Κάτω από την κουκούλα, όλες οι παραλλαγές του προσώπου με ανοιχτό στόμα emoji δείχνουν στο ίδιο σημείο κώδικα U+1F62E, αλλά ο γλύφος που το αντιπροσωπεύει ποικίλλει ανάλογα με την πλατφόρμα;

Τα σημεία κώδικα είναι αφαιρέσεις

Επειδή δεν λένε τίποτα για το πώς αποδίδονται οπτικά (που απαιτούν μια γραμματοσειρά και έναν γλύφο για «να τα φέρουν στη ζωή»), τα σημεία κώδικα λέγεται ότι είναι μια αφαίρεση.

Αλλά όπως τα σημεία κώδικα αποτελούν αφαίρεση για τους τελικούς χρήστες, είναι επίσης αφαιρέσεις για υπολογιστές. Αυτό συμβαίνει επειδή τα σημεία κώδικα απαιτούν κωδικοποίηση χαρακτήρα για να τα μετατρέψουν σε ένα πράγμα που οι υπολογιστές μπορούν να ερμηνεύσουν: bytes. Μόλις μετατραπούν σε byte, τα σημεία κώδικα μπορούν να αποθηκευτούν σε αρχεία ή να σταλούν μέσω δικτύου σε άλλον υπολογιστή; ➡️ ?.

Το UTF-8 είναι σήμερα η πιο δημοφιλής κωδικοποίηση χαρακτήρων στον κόσμο. Το UTF-8 χρησιμοποιεί ένα σύνολο κανόνων για να μετατρέψει ένα σημείο κώδικα σε μια μοναδική ακολουθία (1 έως 4) byte και το αντίστροφο. Τα σημεία κώδικα λέγεται ότι κωδικοποιούνται σε μια ακολουθία byte, και οι ακολουθίες των byte αποκωδικοποιούνται σε σημεία κώδικα. Αυτή η ανάρτηση Stack Overflow εξηγεί πώς λειτουργεί ο αλγόριθμος κωδικοποίησης UTF-8.

Ωστόσο, παρόλο που το UTF-8 είναι ο κυρίαρχος κωδικοποιητής χαρακτήρων στον κόσμο, απέχει πολύ από το μόνο. Για παράδειγμα, το UTF-16 είναι μια εναλλακτική κωδικοποίηση χαρακτήρων του συνόλου χαρακτήρων Unicode. Η παρακάτω εικόνα συγκρίνει τις κωδικοποιήσεις UTF-8 και UTF-16 των emoji μας;

Προβλήματα προκύπτουν όταν ένας υπολογιστής κωδικοποιεί σημεία κώδικα σε byte με μία κωδικοποίηση και άλλος υπολογιστής (ή άλλη διαδικασία στον ίδιο υπολογιστή) αποκωδικοποιεί αυτά τα byte με έναν άλλο.

Ευτυχώς, το 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.

Ευχαριστώ για την ανάγνωση! ;

Απάντηση:

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