Innhold
1 Historikk
Da Cerebrum ble startet på, foregikk nesten all representasjon og utveksling av tekst (på universitetet) i Latin-1.
Likevel var intensjonen at Cerebrum ikke skulle være bundet til et tegnsett. Mulighet for internasjonalisering var nøye gjennomtenkt, men behovet var ikke tilstede i stor nok grad til å ta høyde for språkstøtte (og dermed heller ikke tegnsett) i Cerebrum.
Fordi unicode-objektet ikke ble tatt i bruk tidlig, ble det også fort problemer å kombinere tegnsett. I praksis ble Cerebrum videreutviklet for å benytte seg av ett tegnsett, fra ende til annen.
2 Begrunnelse
Cerebrum trenger støtte for Unicode-data, av flere grunner:
- støtte for et bredere sett med tegn i Cerebrum-data
- støtte for integrasjon med systemer som benytter seg av andre tegnsett
- kompabilitet med Python 3
3 Hva skal være unicode?
Ikke alt kan være unicode. Vi trenger fremdeles bytestrings, f.eks. i kommunikasjon med andre systemer, og i persistent lagring.
Primært vil det være behov for å benytte unicode i all kommunikasjon, hvor tekstdata utveksles med andre systemer:
- Importer
- Eksporter
- Bofhd-klienter
- API-klienter
Interne data, spesielt string literals, trenger ikke være unicode med mindre det er behov for å representere tekst som ikke er i ascii-tegnsettet, eller det er behov for å kombinere tekstdata med annen tekstdata.
4 Unicode-sandwich
Det aller viktigste når det kommer til unicode og bytestrings, er å holde styr på hvordan tekstdata er representert.
Den enkleste måten å gjøre dette på, er å benytte unicode for all tekstdata. Da slipper man å sende metadata (enkoding) med tekstdata som man sender rundt, samt at unicode-objekter har støtte for å representere det meste man kan finne på å representere med tekst.
Dette betyr at vi må:
- Dekode ved input (lese fra fil, request til api-er)
- Enkode ved output (skrive til fil, response til klient)
- Benytte en unicode-kompatibel enkoding i database-kommunikasjon (utf-8), og konfigurere database-modul til å ta imot og gi ut unicode-objekter.
Eventuell bruk av bytestrings internt i Cerebrum må være åpenbart eller godt dokumentert/kommentert. Et typisk område man fremdeles må bruke bytestrings, er bibliotek-metoder som krever dette.
Merk: Encode/decode-sandwich bør også inkludere normalisering, muligens med mulighet for unntak.
5 Normalisering
All unicode-tekst som taes inn i Cerebrum må normaliseres. Uten normalisering mister vi mulighet for å sammenligne tekst. Alle input-data til Cerebrum (importer, API-er) bør normaliseres ved import, altså så tidlig som mulig.
For data internt i Cerebrum, vil det være naturlig å benytte NFC, slik at tegn enkodet i latin-1 dekodes og mappes til "Latin-1 supplement"-blokken i Unicode (æ → U+00E6, ø → U+00F8, å → U+00E5). Dette gjør også at data kommunisert vha. latin-1 til database er normalisert korrekt pr.dd.
Ved output vil det potensielt være behov for/ønske om annen normalisering, og det er helt greit.
For normalisering benyttes unicodedata.normalize.
Det er ikke behov for å gjøre normalisering på innhold som er kjent, og allerede normalisert. F.eks.:
- Dersom import_from_FS normaliserer, trenger man ikke normalisere innhold fra FS-fildumper.
- Tekst som legges inn i database skal allerede være normalisert ved insert/update. Eventuelle feil bør løses av migrasjonsscript.
TODO: Implementere normalisering-wrappers.
6 Translitterasjon
Pr. dd. er det kun behov for translitterasjon ved generering av brukernavn og e-postadresser.
Her finnes allerede funksjonalitet for å behandle et subsett av Latin-1 til ASCII. Det samme subsettet finnes i Unicode, og funksjonaliteten kan portes til å behandle unicode-objekter på samme måte.
Annen eventuell translitterering må taes etter behov (f.eks. eksporter som fremdeles må foregå i en eller annen gitt enkoding).
7 Sortering
Ved behov for sortering av unicode-tekststrenger (hvor tekst kan være forventet å være utenfor ascii), bør vi benytte en modul som gjør dette riktig.
En god kandidat er pyuca.
## six
8 Praktisk
Her kommer en del eksempler og praktiske retningslinjer.
8.1 six
Enkelte steder vil man ha behov for kondisjonell bruk av bytes/unicode avhenging av Python-versjon. Dette kan f.eks. være et metode-kall i standardbiblioteket.
I disse tilfellene skal man benytte seg av six.text_type. Ved første bruk må biblioteket legges inn som requirement.
8.2 logging
All logging skal benytte seg av unicode-objekter. Logg-handler tar seg av enkoding.
8.3 print
print-statements bør generelt ikke benyttes i Cerebrum, men der det evt. forekommer, skal man printe unicode-objekter. Eventuell enkoding skal håndteres automatisk (PYTHONIOENCODING).
8.4 lese og skrive til fil
Filer som skal leses fra og skrives til, bør åpnes med forventet/tilsiktet enkoding (codecs.open). Man kan da skrive unicode-objekter som tekst, og få lest tekst som unicode-objekter.
>>> x = codecs.open('/tmp/foo', mode='Uw', encoding='utf-8')
>>> x.write(u"blåbærsyltetøy\n")
>>> x.close()
$ file /tmp/foo /tmp/foo: UTF-8 Unicode text $ cat /tmp/foo blåbærsyltetøy $ hexdump -C /tmp/foo 00000000 62 6c c3 a5 62 c3 a6 72 73 79 6c 74 65 74 c3 b8 |bl..b..rsyltet..| 00000010 79 0a |y.| 00000012
Forøvrig oppfordres det til bruk av verktøy i codecs for transparent enkoding/dekoding av data ved behov.
8.5 literals
Bruk av literals i kode bør benytte seg av bytestrings, så lenge dette er mulig. Dette gjelder altså tekst som kun skal/kan inneholde tegn fra ASCII-tegnsettet, og som ikke skal kombineres med unicode-objekter.
8.6 tbd
- regex-modul?
- wrapping av stdin/stdout/stderr streams?
9 Fallgruver
9.1 Implisitt dekoding i Python 2
I Python 2 vil bytestrings implisitt dekodes dersom man forsøker å interpolere/konkatenere unicode-objekter og bytestrings:
>>> u"foo" + "bar"
u'foobar'
>>> u"foo%s" % "bar"
u'foobar'
>>> u"foo{}".format("bar")
u'foobar'
Dette vil ikke fungere i Python 3:
>>> "foo" + b"bar" # typeerror
...
TypeError: must be str, not bytes
>>> "foo%s" % b"bar" # repr
"foob'bar'"
>>> "foo{}".format(b"bar") # repr
"foob'bar'"
9.2 Sammenligning i Python 2
I Python 2 vil et unicode-objekt og en bytestring med samme innhold være like:
>>> "foo" == u"foo"
True
>>> "foo".__eq__(u"foo")
NotImplemented
>>> u"foo".__eq__("foo")
True
Dette er ikke tilfelle i Python 3:
>>> "foo" == b"foo"
False
>>> "foo".__eq__(b"foo")
NotImplemented
>>> b"foo".__eq__("foo")
NotImplemented
Dette påvirker også sett, e.g. Python 2:
>>> s = set([u"foo"]) >>> "foo" in s True
Og Python 3:
>>> s = set([b"foo"]) >>> "foo" in s False
9.3 Matching/operasjoner på unicode-tekst
Noen ganger kan det være nødvendig å lese filer hvor dekoding gjøres etter annen tekstbehandling (som f.eks. splitting på linje) ↓.
>>> a = u'hello\N{LINE SEPARATOR}world'
>>> len(a.split('\n'))
1
>>> len(a.splitlines())
2