Eine anfängerfreundliche Anleitung zu Unicode in Python

Ich habe einmal ein paar frustrierende Tage bei der Arbeit verbracht, um zu lernen, wie man mit Unicode-Strings in Python richtig umgeht. Während dieser zwei Tage habe ich viele Snacks gegessen - ungefähr eine Tüte Goldfisch pro einen dieser Fehler, die denjenigen, die mit Python programmieren, nur allzu vertraut sein sollten:

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

Während ich mein Problem löste, googelte ich viel, was mich auf einige unverzichtbare Artikel hinwies. Aber so großartig sie auch sind, sie wurden alle ohne die Hilfe eines entscheidenden Aspekts der Kommunikation in der heutigen Zeit geschrieben.

Das heißt: Sie wurden alle ohne die Hilfe von Emoji geschrieben.

Um diese Situation auszunutzen, habe ich beschlossen, meinen eigenen Leitfaden zum Verständnis von Unicode zu schreiben, auf dem viele Gesichter und Symbole gerendert werden.

Bevor wir uns mit technischen Details befassen, beginnen wir mit einer lustigen Frage. Was ist dein Lieblings-Emoji?

Meins ist das "Gesicht mit offenem Mund", das so aussieht? - mit einer großen Einschränkung. Was Sie sehen, hängt tatsächlich von der Plattform ab, auf der Sie diesen Beitrag lesen!

Auf meinem Mac sieht das Emoji aus wie eine gelbe Bowlingkugel. Auf meinem Samsung-Tablet sind die Augen schwarz und kreisförmig, hervorgehoben durch einen weißen Punkt, der eine größere Tiefe von Emotionen verrät.

Kopieren Sie das Emoji (?) Und fügen Sie es in Twitter ein, und Sie werden etwas völlig anderes sehen. Kopieren Sie es jedoch und fügen Sie es in messenger.com ein, und Sie werden sehen, warum es mein Favorit ist.

???? Warum sind sie alle unterschiedlich?

Hinweis: Ab dem 9. Juli 2018: Messenger hat anscheinend seine Emoji-Symbole aktualisiert, sodass das Symbol oben rechts nicht mehr gilt. ?

Dieses lustige kleine Rätsel ist unser Einstieg in die Welt von Unicode, da Emojis seit 2010 Teil des Unicode-Standards sind. Neben Emoji ist Unicode wichtig, da es die bevorzugte Wahl des Internets für die konsistente „Codierung, Darstellung und Umgang mit Text “.

Unicode & Encoding: Eine kurze Einführung

Wie bei vielen Themen ist der beste Weg, um Unicode zu verstehen, den Kontext zu kennen, der mit seiner Erstellung verbunden ist - und dafür muss Joel Spolskys Artikel gelesen werden.

Codepunkte

Da wir jetzt in die Welt von Unicode eingetreten sind, müssen wir zuerst Emojis von den wunderbar ausdrucksstarken Symbolen trennen und sie mit etwas viel weniger Aufregendem assoziieren. Anstatt über Emojis in Bezug auf die Dinge oder Emotionen nachzudenken, die sie darstellen, werden wir stattdessen über jedes Emoji als einfache Zahl nachdenken. Diese Nummer wird als Codepunkt bezeichnet .

Codepunkte sind das Schlüsselkonzept von Unicode, das „den weltweiten Austausch, die Verarbeitung und die Anzeige der geschriebenen Texte der verschiedenen Sprachen… der modernen Welt unterstützen soll“. Dazu wird praktisch jedes druckbare Zeichen einem eindeutigen Codepunkt zugeordnet. Zusammen bilden diese Zeichen den Unicode- Zeichensatz .

Codepunkte werden normalerweise hexadezimal geschrieben und mit einem Präfix versehen U+, um die Verbindung zu Unicode zu kennzeichnen. Dabei handelt es sich um Zeichen aus:

  • exotische Sprachen wie Telugu [ఋ | Codepunkt: U + 0C0B]
  • Schachsymbole [♖ | Codepunkt: U + 2656]
  • und natürlich Emojis [? | Codepunkt: U + 1F64C]

Glyphen sind das, was Sie sehen

Die tatsächliche Bildschirmdarstellung von Codepunkten wird als Glyphen bezeichnet (dievollständige ZuordnungCodepunkte auf Glyphen als bekannte Schrift ) .

Als Beispiel , nehmen diesen Brief A, der Codepunkt ist U+0041in Unicode. Das „A“, das Sie mit Ihren Augen sehen, ist eine Glyphe - es sieht genauso aus, weil es mit der Schriftart von Medium gerendert wird. Wenn Sie die Schriftart in "Times New Roman" ändern würden, würde sich nur die Glyphe "A" ändern - der zugrunde liegende Codepunkt nicht.

Glyphen sind die Antwort auf unser kleines Rendering-Rätsel. Unter der Haube zeigen alle Variationen des Gesichts mit offenem Mund-Emoji auf denselben Codepunkt U+1F62E, aber die Glyphe, die ihn darstellt, variiert je nach Plattform?

Codepunkte sind Abstraktionen

Da sie nichts darüber aussagen, wie sie visuell gerendert werden (eine Schriftart und eine Glyphe sind erforderlich, um sie zum Leben zu erwecken), werden Codepunkte als Abstraktion bezeichnet.

Genauso wie Codepunkte eine Abstraktion für Endbenutzer sind, sind sie auch Abstraktionen für Computer. Dies liegt daran, dass Codepunkte eine Zeichencodierung erfordern , um sie in das zu konvertieren, was Computer interpretieren können: Bytes. Einmal in Bytes konvertiert, können Codepunkte in Dateien gespeichert oder über das Netzwerk an einen anderen Computer gesendet werden.

UTF-8 ist derzeit die weltweit beliebteste Zeichenkodierung. UTF-8 verwendet eine Reihe von Regeln, um einen Codepunkt in eine eindeutige Folge von (1 bis 4) Bytes umzuwandeln und umgekehrt. Codepunkte sollen in eine Folge von Bytes codiert werden, und Sequenzen von Bytes werden in Codepunkte decodiert . In diesem Beitrag zum Stapelüberlauf wird erläutert, wie der UTF-8-Codierungsalgorithmus funktioniert.

Obwohl UTF-8 die weltweit vorherrschende Zeichenkodierung ist, ist es bei weitem nicht die einzige. Beispielsweise ist UTF-16 eine alternative Zeichenkodierung des Unicode-Zeichensatzes. Das Bild unten vergleicht die UTF-8- und UTF-16-Codierungen unseres Emoji?.

Probleme treten auf, wenn ein Computer Codepunkte mit einer Codierung in Bytes codiert und ein anderer Computer (oder ein anderer Prozess auf demselben Computer) diese Bytes mit einem anderen decodiert.

Glücklicherweise ist UTF-8 allgegenwärtig genug, dass wir uns größtenteils keine Gedanken über nicht übereinstimmende Zeichenkodierungen machen müssen. Wenn sie jedoch auftreten, ist eine Kenntnis der oben genannten Konzepte erforderlich, um sich aus dem Chaos zu befreien.

Kurze Zusammenfassung

  • Unicode ist eine Sammlung von Codepunkten , bei denen es sich um einfache Zahlen handelt, die normalerweise hexadezimal geschrieben und mit einem Präfix versehen sind U+. Diese Codepunkte werden praktisch jedem druckbaren Zeichen aus den geschriebenen Sprachen auf der ganzen Welt zugeordnet.
  • Glyphen sind die physische Manifestation eines Charakters. Dieser Typ ? ist eine Glyphe. Ein F ont ist eine Zuordnung von Codepunkten zu Glyphen.
  • Um sie über das Netzwerk zu senden oder in einer Datei zu speichern, müssen Zeichen und ihre zugrunde liegenden Codepunkte in Bytes codiert werden. Eine Zeichenkodierung enthält die Details, wie ein Codepunkt in eine Folge von Bytes eingebettet wird.
  • UTF-8 ist derzeit die weltweit beliebteste Zeichenkodierung. Bei einem gegebenen Codepunkt codiert UTF-8 ihn in eine Folge von Bytes. Bei einer gegebenen Folge von Bytes decodiert UTF-8 diese in einen Codepunkt.

Ein praktisches Beispiel

Das korrekte Rendern von Unicode-Zeichen umfasst das Durchlaufen einer Kette, die von Bytes über Codepunkte bis hin zu Glyphen reicht.

Lassen Sie uns nun einen Texteditor verwenden, um ein praktisches Beispiel für diese Kette zu sehen - sowie die Arten von Problemen, die auftreten können, wenn etwas schief geht. Texteditoren sind perfekt, da sie alle drei Teile der oben gezeigten Renderkette betreffen.

Hinweis: Das folgende Beispiel wurde auf meinem MacOS mit Sublime Text 3 erstellt. Und um Kredit zu geben, wo Kredit fällig ist: Der Anfang dieses Beispiels ist stark von diesem Beitrag von Philip Guo inspiriert, der mich in den hexdumpBefehl (und eine ganze Menge) eingeführt hat Mehr).

Wir beginnen mit einer Textdatei, die ein einzelnes Zeichen enthält - mein Lieblings-Emoji „Gesicht mit offenem Mund“. Für diejenigen, die mitmachen möchten, habe ich diese Datei in einem Github-Gist gehostet, mit dem Sie lokal arbeiten curl.

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

Wie wir erfahren haben, wurde das Emoji zum Speichern in einer Datei mithilfe einer Zeichencodierung in Bytes codiert. Diese bestimmte Datei wurde mit UTF-8 codiert, und wir können den hexdumpBefehl verwenden, um den tatsächlichen Byte-Inhalt der Datei zu untersuchen.

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