Mikroprobléma

Ha mértékegységekkel dolgozunk, akkor szükség lehet egy olyan függvényre, amely ellenőrzi, hogy az argumentumként megadott karaktersorozat egy, a mértékegységek nagyságrendjét meghatározó SI előtag jele-e, és ha igen, akkor visszaadja az előtag által jelölt szorzótényezőt. Ez elég egyszerű feladatnak tűnik, mert az előtagjel és szorzótényező megfeleltetéseket egy szótárban kell tárolni, és a függvény e szótárnak a függvény argumentumhoz mint kulcshoz tartozó értékét szolgáltatja, ha a kulcs a szótárban található. Ha nem, akkor egy kivételt dob. Ezt látjuk alább:

A kiírásokból látható, hogy az előtagjelekhez megkapjuk a szorzókat egy kivételével. A  µ karakterrel jelölt „mikro” előtag esetén hibajelzést kapunk, mondván, hogy ilyen jel nem szerepel a szótárban. Elsőre ez elég meglepő, hiszen látjuk a µ betűt a szótárban. Mi lehet a gond?

Gyanús lehet, hogy éppen a µ esetén van a probléma, mert az SI előtagjelek közül ez az egyetlen, amely nem a latin ábécéből származik. A többiek ráadásul az ASCII készlet betűi. Lehet, hogy akkor a karakterkészlet körül kell keresni az okokat? Elég valószínű, de ahhoz, hogy megállapítsuk, hogy miért, első menetben egy kicsit tájékozódjuk az SI előtagokról, ezen belül is arról, hogy milyen Unicode karakterrel kell jelölni hivatalosan a micro előtagot. Legyorsabban a Wikipedia „Micro-” szócikkében találjuk meg a választ. Ebből kiderül, hogy a micro előtagra két Unicode karakter is használatos. Az egyik az U+00B5 (MICRO SIGN), a másik az U+03BC (GREEK SMALL LETTER MU). És azt is megtudjuk, hogy ez utóbbi az SI által hivatalosan támogatott karakter.

Ha lekérjük az ord() beépített függvénnyel a szótárban szereplő  µ betű és az argumentumként átadott µ karakter kódpontját reprezentáló egész számot, akkor derül ki, hogy mi a probléma. A szótárban az előtagként hivatalosan is támogatott U+03BC kódpontú karakter szerepel, a függvény hívásakor azonban az U+00B5 kódpontú karakter lett bevíve. A hiba okát pedig azért nem látjuk azonnal, mert a vizuális megjelenése a kettőnek azonos.

Ha tudnánk előre, hogy a függvénynek a görög kis mű betűt kell megadni, akkor is némi technikai kényelmetlenségbe ütközhetünk. Ugyanis a normál számítógépbillentyűzeten nincs µ karakter. Csak billentyűkombinációval tudunk ilyet beilleszteni, ami platformfüggő. Windows alatt az Alt gomb lenyomva tartása mellett a numerikus billentyűzeten a 0181 számsort kell beírni. Ezzel azonban a feladatunk szempontjából az a baj, hogy nem az Unicode szerinti görög betűt eredményezi, hanem a MICRO SIGN karaktert. A görög mű betűhöz viszont nincs Alt-kód. Tehát, ha ezt akarjuk megkapni, akkor más megoldást kell keresni.

Egy platformfüggetlen lehetőség, hogy a neten rákeresünk az Unicode GREEK SMALL LETTER MU keresőkifejezéssel, és az ott megjelenő karakterképet kimásoljuk. Ha Windows alatt dolgozunk, akkor egy másik, viszonylag egyszerű megoldás, ha a beépített Character Map alkalmazást elindítjuk és itt keressük meg, majd kimásoljuk. Ezt a módszert mutatja a következő ábra, ahol a teendőket a bekarikázott számok sorrendjében kell végezni.

Tovább bonyolítja a helyzetet, hogy az Unicode készletben nem csak ez a két karakter jelenik meg µ karakterként. Az összes, µ karakterképpel rendelkező karakter jellemzőit az alább látható programkóddal íratjuk ki. Itt a karakternevek kinyeréséhez a szabványos könyvtár unicodedata moduljának name függvényét használtuk.

A karakterek e többértelműsége – vagyis az, hogy hasonló vizuális megjelenéshez különböző absztrakt karakterekhez tartozhatnak – nem csak a µ karakter kapcsán merül fel az Unicode karakterkészletben. És ez a programokban hasonló gondot okozhat, mint a kiinduló függvényünk esetében. Ezen úgy lehetne segíteni, ha a megjelenési formában hasonló karaktereket valamilyen elv szerint „közös nevezőre” lehetne hozni. Ekkor a bemenetként megadott karakterláncokat erre lehetne konvertálni, és így az egyenlőségvizsgálatnál már nem lenne abból adódó eltérés, hogy a szemantikailag vagy vizuális formában hasonló bevitt karakterek különböző kódpontokhoz tartoznak.

A Unicode szabvány e problémát a karakterek kétfajta egyenértékűségének meghatározásával, és ezek alapján történő normalizálással oldja meg. E fogalmakat tekintjük át a következőkben.

Unicode-egyenértékűség és fajtái

Az Unicode-egyenértékűség azt jelenti, hogy bizonyos kódpont-sorozatok lényegében ugyanazt a karaktert képviselik. Az egyenértékű karakterek azonosak vagy hasonlóak. A karaktsorozatok két módon lehetnek egyenértékűek: kanonikusan egyenértékűek vagy kompatibilisek.

A kanonikusan egyenértékű kódpontok megjelenése és jelentése megegyezik. Például az U+0065 (e), amelyet egy, önállóan nem használható, a megelőző karakterhez kapcsolódó ékezetjel, az U+0301 (◌́) követ, kanonikusan egyenértékű az U+00E9 (é) kódponttal. E kettő megjelenésében pontosan megegyezik, és karakterek rendezésekor vagy keresésekor egymással helyettesíthető.

A kompatibilis kódpontok eltérően jelenhetnek meg, de meghatározott kontextusokban azonos jelentéssel bírnak. Például az U+00BD (½) kompatibilis az U+0031 U+2044 U+0032 (1⁄2) karaktersorozattal, de nem kanonikusan egyenértékű. A kompatibilis sorozatok adott alkalmazásokban ugyanúgy használhatók, illetve helyettesíthetők.

Unicode-normalizálás

A Unicode két fajta egyenértékűsége teremti meg annak lehetőségét, hogy a szoftverekben a szövegfeldolgozáskor a keresés és összehasonlítás a tervezettnek megfelelően, helyesen működjön. Ennek feltétele, hogy a feldolgozni kívánt karaktersorozatnak elő tudjuk állítani az egyenértékű változatát.

A karakterláncok egyenértékű sorozatát a normalizálásnak nevezett folyamat határozza meg, amely a karakterláncokat olyan formára alakítja, amelyek közvetlenül összehasonlíthatók.

A Unicode-normalizálás célja tehát, hogy a többféleképpen kódolható karaktereket egyetlen, szabványos formátumra alakítsa. Ezt nevezik az eredeti karaktersorozat normalizált formájának.

A szabvány négy normalizációs formát határoz meg. Ez két szempont együttes alkalmazásának eredménye:

  • a normalizálás kanonikus felbontást vagy kompatibilitási felbontást alkalmaz, és
  • a normalizált forma az egyenértékű sorozatok lehető legteljesebb felbontásával, vagy összetételével jön létre.

Unicode normalizációs formák

D normalizációs forma (Normalization Form D, NFD):

A karaktersorozat szabvány szerint kanonikusan egyenértékű karakterekre bontása, majd ezek kanonikus rendezése.

KD normalizációs forma (Normalization Form KD, NFKD):

A karaktersorozat szabvány szerint kompatibilisen vagy kanonikusan egyenértékű karakterekre bontása, majd ezek  kanonikus rendezése.

C normalizációs forma (Normalization Form C, NFC):

A karaktersorozat szabvány szerint kanonikusan egyenértékű karakterekre bontása, ezek kanonikus rendezése, majd az így kapott sorozat kanonikus összetétele.

KC normalizációs forma (Normalization Form KC, NFKC):

A karaktersorozat szabvány szerint kompatibilisen vagy kanonikusan egyenértékű karakterekre bontása, ezek kanonikus rendezése, majd az így kapott sorozat kanonikus összetétele.

Ha egy karakterhez nem tartozik dekompozíciós leképezés, akkor a karakter változatlan marad.

A kompatibilitási felbontás nem helyettesíti, hanem kiterjeszti a kanonikust: minden kanonikus felbontás kompatibilitási értelemben is megengedett. De nem minden kompatibilitási felbontás kanonikus.

A „felbontás” (decomposition) itt tágabb értelemű fogalom, mert előfordul, hogy egy karakter dekompozíciója egyetlen másik karakterből áll.

Unicode kompatibilitási kategóriák

A karaktersorozatok közötti kompatibilitás több ismérv szerint is meghatározható, amelyek alapján kompatibilitási kategóriák hozhatók létre. Az Unicode e kategóriáknak megfelelő kompatibilitási címkéket rendel a karakterekhez és ezt követően megadja a kompatibilis karaktert vagy karaktersorozatot. A kategóriák és címkék a következő táblázatában vannak felsorolva.

Ha meg akarjuk tudni, hogy egy adott karakterhez van-e hozzárendelt felbontás, és ha igen milyen, akkor az unicodedata modul decomposition() függvényét kell meghívni az adott karakterre. Az alábbi tesztprogramban néhány példát mutatunk. Ezek között szerepel a görög mű betű is, valamint az összes, korábban felsorolt ehhez hasonló karakter. Láthatjuk, hogy ez utóbbiak mindegyikének van felbontása, mégpedig kompatibilitási, ami minden esetben a U+03BC kódpontú görög mű betű.

Normalizálás megvalósítása a progamkódban

Mindezek után a kérdés már csak az, hogy hogyan tudjuk egy karakter vagy karaktersorozat Unicode-normalizálását a programunkban elvégezni. Ehhez nem kell mást tenni, mint az unicodedata modul normalize() függvényét kell meghívni, átadva az adott karaktersorozatot mint második argumentumot. Visszatérési értékként a normalizált karaktersorozatot kapjuk.

Az eddig tárgyalt elvi alapok megismerésére nem csak azért volt szükség, hogy a decomposition() függvény célját értsük és tudjuk értelmezni a visszatérési értékét, hanem azért is, hogy a normalize() függvényt értsük és helyes tudjuk használni. Ugyanis első paraméterének meg kell adni egy konstanst, ami meghatározza, hogy a négy lehetséges normalizációs forma közül melyik alapján kívánjuk a normalizálást végezni. A konstans értékei NFC, NFKC‘, NFD, NFKD lehetnek, amelyek a normalizációs formák angol nevéből képzett rövidítések, ahogy azt fentebb már láttuk.

De mi alapján döntsük el, hogy melyik normalizációs formát válasszuk? Más szóval, mi alapján határozzuk meg, hogy a normalize() függvény hívásához melyik konstanst kell átadni?

A választás alapja a cél és a kontextus. A Unicode-normalizáció célja mindig az, hogy azonosításhoz, összehasonlításhoz, kereséshez, tároláshoz megbízható, egységes reprezentációt kapjunk. Ezért a választást elsősorban az alkalmazás, a használati cél határozza meg.  A következő táblázat ehhez ad néhány szempontot és javaslatot.

Most már visszatérhetünk az eredeti feladatunkhoz. Az SI előtaghoz tartozó tényezőt kiadó függvényünk esetében az argumentumként kapott karakterláncot, miután megtisztítottuk ez esetleges határoló szóközöktől, először normalizálni kell, és csak aztán lehet a szótárból kikeresni az ehhez tartozó szorzót. Mivel itt adott karakterláncra vonatkozó egyenlőségvizsálatot végzünk, ezért az NFKC normalizációs formát választjuk a normalize() függvény hívásához.  Az így módosított függvény definícióját és a µ karakterekre vonatkozó tesztsorokat alább láthatjuk.

Az eredmények visszaigazolják, hogy a függvény most már a tervezetteknek megfelelően, helyesen működik.

Ebben a bejegyzésben az Unicode karakterek és feldolgozásuk volt a középpontban. Az Unicode rendszer alapjainak ismerete nem csak az itt bemutatott példa megértéséhez, hanem a napi Python alkalmazásfejlesztéshez is nélkülözhetetlen. Ezért a Python tudásépítés lépésről lépésre című e-könyv már az elején, a „Unicode röviden” című részben ismerteti a legalapvetőbb tudnivalókat.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.