Pradedantiesiems skirtas „Unicode“ vadovas „Python“

Kartą praleidau porą apmaudžių dienų darbe mokydamasi, kaip tinkamai elgtis su „Unicode“ eilutėmis „Python“. Per tas dvi dienas suvalgiau daug užkandžių - maždaug po vieną auksinių žuvelių maišą vienoje iš šių klaidų, kurios turėtų būti per daug žinomos tiems, kurie programuoja su „Python“:

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

Spręsdamas savo problemą, aš daug googlinau, kuris atkreipė dėmesį į keletą būtinų straipsnių. Kad ir kokie puikūs, jie visi buvo parašyti be svarbiausio šių dienų bendravimo aspekto.

Tai yra: jie visi buvo parašyti be jaustukų pagalbos.

Taigi, norėdamas pasinaudoti šia situacija, nusprendžiau parašyti savo „Unicode“ supratimo vadovą, kuriame būtų daugybė veidų ir piktogramų? ✌ ?.

Prieš pasinerdami į technines detales, pradėkime nuo smagaus klausimo. Kokia jūsų mėgstamiausia jaustukas?

Mano „veidas atvira burna“, kuris atrodo taip? - su vienu dideliu įspėjimu. Tai, ką matote, iš tikrųjų priklauso nuo platformos, kurią naudojate skaitant šį įrašą!

Žiūrint per „Mac“, jaustukas atrodo kaip geltonas boulingo kamuolys. Mano „Samsung“ planšetiniame kompiuteryje akys yra juodos ir apvalios, jas pabrėžia baltas taškas, išduodantis didesnį emocijų gylį.

Nukopijuokite ir įklijuokite jaustukus (?) Į „Twitter“ ir pamatysite ką nors kitą. Nukopijuokite ir įklijuokite jį į messenger.com, tačiau pamatysite, kodėl jis yra mano mėgstamiausias.

???? Kodėl jie visi skirtingi?

Pastaba: Nuo 2018 m. Liepos 9 d., Atrodo, kad „Messenger“ atnaujino savo jaustukų piktogramas, todėl viršuje dešinėje esanti piktograma nebetaikoma. ?

Ši smagi paslaptis yra mūsų segmentas „Unicode“ pasaulyje, nes nuo 2010 m. Jaustukai yra „Unicode“ standarto dalis. Be to, kad suteikiame jaustukus, „Unicode“ yra svarbus, nes tai yra tinkamiausias interneto pasirinkimas nuosekliam „kodavimui, vaizdavimui ir teksto tvarkymas “.

„Unicode“ ir kodavimas: trumpas pradas

Kaip ir daugelio temų atveju, geriausias būdas suprasti „Unicode“ yra žinoti jo sukūrimo kontekstą - ir tam reikia perskaityti Joelio Spolsky straipsnį.

Kodo taškai

Kadangi dabar patekome į „Unicode“ pasaulį, pirmiausia turime atskirti jaustukus nuo nuostabiai išraiškingų piktogramų, kurie jie yra, ir susieti juos su kažkuo mažiau įdomiu. Taigi, užuot galvoję apie jaustukus apie dalykus ar emocijas, kuriuos jie atstovauja, mes galvosime apie kiekvieną jaustuką kaip paprastą skaičių. Šis skaičius žinomas kaip kodo taškas .

Kodo taškai yra pagrindinė „Unicode“ koncepcija, kuri „buvo sukurta siekiant palaikyti įvairių pasaulio kalbų ... šiuolaikinio pasaulio rašytinių tekstų mainus, apdorojimą ir rodymą visame pasaulyje“. Tai daro susiedamas praktiškai kiekvieną spausdinamą simbolį su unikaliu kodo tašku. Šie simboliai kartu sudaro „Unicode“ simbolių rinkinį .

Kodo taškai paprastai rašomi šešioliktainiais U+ženklais ir prieš juos dedami žymėti ryšį su „Unicode“, žymintys simbolius iš:

  • egzotinių kalbų, tokių kaip telugų [ఋ | kodo taškas: U + 0C0B]
  • šachmatų simboliai [♖ | kodo taškas: U + 2656]
  • ir, žinoma, jaustukai [? | kodo taškas: U + 1F64C]

Grifai yra tai, ką matote

Faktinis ekrano kodo taškų atvaizdavimas vadinamas simboliais (pilnas kartografavimaskodų taškų į simbolius yra žinomas kaip šriftas ) .

Pavyzdžiui , pasinaudoti šia raide A, kuris yra kodas taškas U+0041Unicode. „A“, kurį matote akimis, yra glifas - atrodo taip, kaip atrodo, nes jis pateikiamas naudojant „Medium“ šriftą. Jei pakeistumėte šriftą į, pavyzdžiui, „Times New Roman“, pasikeistų tik „A“ simbolis - pagrindinis kodo taškas nepasikeistų.

Grifai yra atsakymas į mūsų mažą perteikimo paslaptį. Po gaubtu visos veido su atvira burna emocijos variacijos rodo tą patį kodo tašką U+1F62E, tačiau jį vaizduojantis simbolis skiriasi pagal platformą?

Kodo taškai yra abstrakcijos

Kadangi jie nieko nesako apie tai, kaip jie vizualiai perteikiami (jiems reikalingas šriftas ir glifas, kad „juos atgaivintų“), sakoma, kad kodo taškai yra abstrakcija.

Bet kaip kodo taškai yra abstrakcija galutiniams vartotojams, jie taip pat yra abstrakcijos kompiuteriams. Taip yra todėl, kad kodo taškai reikalauja simbolių kodavimo, kad juos paverstų vienu dalyku, kurį kompiuteriai gali interpretuoti: baitais. Konvertavus į baitus, kodo taškus galima išsaugoti failuose arba per tinklą nusiųsti į kitą kompiuterį? ➡️ ?.

Šiuo metu UTF-8 yra populiariausias pasaulyje simbolių kodavimas. UTF-8 naudoja taisyklių rinkinį, kad kodo tašką paverstų unikalia (nuo 1 iki 4) baitų seka ir atvirkščiai. Sakoma, kad kodo taškai yra užkoduoti baitų sekoje, o baitų sekos dekoduojamos į kodo taškus. Šiame „Stack Overflow“ įraše paaiškinama, kaip veikia UTF-8 kodavimo algoritmas.

Tačiau nors UTF-8 yra vyraujantis simbolių kodavimas pasaulyje, jis toli gražu nėra vienintelis. Pavyzdžiui, UTF-16 yra alternatyvi simbolių rinkinio „Unicode“ simbolių koduotė. Žemiau pateiktame paveikslėlyje palyginami mūsų jaustukų UTF-8 ir UTF-16 kodavimai?

Problemų kyla, kai vienas kompiuteris koduoja taškus į baitus su vienu kodavimu, o kitas kompiuteris (ar kitas procesas tame pačiame kompiuteryje) tuos baitus dekoduoja kitu.

Laimei, UTF-8 yra pakankamai visur, kad didžiąja dalimi mums nereikėtų jaudintis dėl netinkamų simbolių koduočių. Bet kai jų pasitaiko, reikia išmanyti aukščiau paminėtas sąvokas, kad išsivaduotum iš netvarkos.

Trumpas apibendrinimas

  • „Unicode“ yra kodo taškų rinkinys , kuris yra paprastas skaičius, paprastai parašytas šešioliktainiu skaičiumi ir su priešais U+. Šis kodas nurodo praktiškai kiekvieną spausdinamą simbolį iš rašytinių kalbų visame pasaulyje.
  • Glifai yra fizinis personažo pasireiškimas. Šis vaikinas ? yra glifas. Af ont yra kodo kiekis kartografavimo molio.
  • Norint juos išsiųsti per tinklą arba išsaugoti faile, simboliai ir jų pagrindiniai kodo taškai turi būti užkoduoti baitais. Koduotė yra apie tai, kaip kodas, vieta yra įdėta į baitų seka detales.
  • UTF-8 šiuo metu yra populiariausias pasaulyje simbolių kodavimas. Atsižvelgdamas į kodo tašką, UTF-8 jį užkoduoja į baitų seką. Atsižvelgiant į baitų seką, UTF-8 ją dekoduoja į kodo tašką.

Praktinis pavyzdys

Teisingas „Unicode“ simbolių perteikimas apima grandinės perėjimą nuo baitų iki kodo taškų ir simbolių.

Dabar naudokime teksto rengyklę, kad pamatytume praktinį šios grandinės pavyzdį - taip pat problemų, kurios gali kilti, kai viskas nesiseka, tipus. Teksto redaktoriai yra tobuli, nes juose yra visos trys aukščiau pateiktos atvaizdavimo grandinės dalys.

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.

Ačiū, kad skaitėte! ?

Atsakymas:

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