Hoppa yfir í efnið

Strengir og bæti

Þegar unnið er með texta er mikilvægt fyrir okkur að vera upplýst um hvenær við erum að vinna með kóðaðan texta og hvenær við erum að vinna með hrá bæti. Í þessum kafla munum við sjá hvernig hægt er að vinna með þessar tvær tegundir gagna og hvernig Python gerir greinarmun á þeim.


Texti undir yfirborðinu

Python býður upp á strengi (e. strings), þ.e. gagnatagið str, til að geyma texta. Python notast við UTF-8 til að kóða strengi, og er því hægt að geyma hvaða tákn sem er í strengjum, jafnvel broskalla.

Auk hefðbundinna strengja býður Python einnig upp á gagnatagið bætastrengi, bytes, sem er ætlað að halda utan um tvíundagögn, þ.e. bætarunur; lista þar sem hvert gildi er á milli 0 og 255.

Strengi má skilgreina með ýmsum aðferðum, ýmist með einföldum eða tvöföldum gæsalöppum, og einnig með þremur gæsalöppum í röð til að skilgreina strengi sem spanna margar línur.

>>> "strengur"
'strengur'

>>> '🐯🐴🚢🎄'
'🐯🐴🚢🎄'

>>> '''strengur
sem
spannar
nokkrar
línur
'''
'strengur\nsem\nspannar\nnokkrar\nlínur\n'

>>> """annar
    strengur"""
'annar\n    strengur'

Bætastreng má skilgreina á sama máta með b á undan gæsalöppunum. Bætastrengir geta aðeins innihaldið ASCII tákn eða hrá bæti sem hafa ritháttinn \xXX, þar sem XX er gildi bætisins í sextándakerfinu.

>>> b"hello"
b'hello'

>>> b'\x68\x65\x6c\x6c\x6f\x00'
b'hello\x00'

>>> b"halló"
SyntaxError: bytes can only contain ASCII literal characters

Mikilvægt er að hafa í huga með hvaða tag við erum að vinna. Strengir og bætastrengir eru eðlislega svipaðir og deila mörgum aðgerðum (e. class methods), en í flestum tilfellum taka þessar aðgerðir færibreytur af sama tagi. Tökum replace sem dæmi:

>>> 'hello'.replace('e', 'a')
'hallo'

>>> b'hello'.replace(b'e', b'a')
b'hallo'

>>> b'hello'.replace('e', 'a')
TypeError: a bytes-like object is required, not 'str'

>>> 'hello'.replace(b'e', b'a')
TypeError: replace() argument 1 must be str, not bytes

Kóðun og afkóðun

Til að færa texta yfir á bætaform, og öfugt, býður Python upp á tvær aðgerðir: encode og decode. Fyrri aðgerðin, encode, er aðgerð á strengjum og skilar bætastreng með strengnum kóðuðum með valdri textakóðun. Nafn kóðunarinnar er gefið sem strengur og styður Python margar kóðanir. Sjálfgefin kóðun er UTF-8, sé engin kóðun valin. Sú seinni, decode, er aðgerð á bætastrengjum og skilar okkur streng (UTF-8 kóðuðum) sem fæst með því að afkóða bætastrenginn með valinni textakóðun.

       Texti (str)  "hæ"
            │
            │  .encode("utf-8")  (Kóðun)
            ▼
       Bæti (bytes) b"h\xc3\xa6"
            │
            │  .decode("utf-8")  (Afkóðun)
            ▼
       Texti (str)  "hæ"

Prófum þetta í gagnvirku skelinni.

>>> texti = "hæ"
>>> baeti = texti.encode("utf-8")
>>> baeti
b'h\xc3\xa6'
>>> baeti.decode()  # UTF-8 er sjálfgefin kóðun
'hæ'

Tökum eftir því að stafurinn h birtist óbreyttur í bætastrengnum því hann er í ASCII, en íslenski stafurinn æ er sýndur sem bætin \xc3\xa6 (0xc3 og 0xa6) sem saman tákna stafinn æ í UTF-8 kóðuninni.

Þegar hlutirnir fara úrskeiðis

Ef reynt er að afkóða bætastreng sem inniheldur kóðun sem stemmir ekki við neina UTF-8 stafi, mun Python gefa villuna UnicodeDecodeError. Þetta er algengt vandamál og getur bent til þess að röng kóðun hafi verið valin, t.d. að ISO-8859-1 kóðaður texti hafi verið afkóðaður sem UTF-8, eða að gögnin hafi skemmst á einhvern hátt, t.d. vegna gallaðrar dulráðningar. Ef unnið er með skemmd gögn er hægt að hunsa villurnar með færibreytunni errors="ignore", sem sleppir því að kóða bæti sem ekki stemma við valinn staðal, eða errors="replace" sem skiptir öllum bætum sem ekki stemma við valinn staðal út fyrir ``.

>>> 'æíóú'.encode('latin1').decode('utf-8', errors='ignore')
''
>>> 'æíóú'.encode('latin1').decode('utf-8', errors='replace')
''

Gagnakóðun

Sextándakerfiskóðun

Hægt er að fá sextándakerfiskóðun bætastrengja með aðgerðinni hex, þ.e.

>>> b"ABC".hex()
'414243'

Til að afkóða slíkan streng má svo nota bytes.fromhex,

>>> bytes.fromhex("414243")
b'ABC'

sem skilar afkóðuðu gögnunum sem bætastreng.

Önnur leið

Einnig er hægt að notast við innbyggða kóðasafnið binascii sem býður upp á föllin hexlify og unhexlify til að kóða og afkóða gögn með sextándakerfiskóðun. Eini munurinn á þessu kóðasafni og innbyggðu aðgerðunum er að hexlify skilar bætastreng, á meðan hex aðgerðin skilar streng.

>>> import binascii

# binascii.hexlify kóðar bætastreng
>>> binascii.hexlify(b"ABC")
b'414243'

# binascii.unhexlify afkóðar kóðaðan streng
>>> binascii.unhexlify("414243")
b'ABC'

Base64, Base32 og Base85

Í kaflanum um gagnakóðun kynntumst við kóðununum Base64, Base32 og Base85. Python býður upp á innbyggða forritasafnið base64 til að kóða og afkóða gögn á því formi (sem og fleirum). Föllin í forritasafninu fylgja þeirri hefð að forskeytið segir til um kóðunina og viðskeytið er ýmist encode eða decode, eftir því hvort framkvæma eigi kóðun eða afkóðun. Kóðunarföllin taka ávallt við bætastrengjum á meðan afkóðunarföllin geta tekið við hvoru tveggja, bætastrengjum eða strengjum.

Fall Lýsing
b64encode Base64 kóðun
b64decode Base64 afkóðun
b32encode Base32 kóðun
b32decode Base32 afkóðun
a85encode Ascii85 (Base85) kóðun.
a85decode Ascii85 afkóðun.

Tökum dæmi um kóðun og afkóðun í Base64.

from base64 import b64encode, b64decode

>>> gogn = b"GGFI"
>>> b64encode(gogn)
b'R0dGSQ=='

>>> base64.b64decode("R0dGSQ==")
b'GGFI'

Til að vinna með kóðuðu gögnin sem streng, en ekki bætastreng, getum við notað decode, þ.e.

>>> gogn = b"GGFI"
>>> kodad = b64encode(gogn)
>>> kodad
b'R0dGSQ=='
>>> kodad.decode()
'R0dGSQ=='

Kóðun með Base32 og Ascii85 er sambærileg.

from base64 import b32encode, b32decode, a85encode, a85decode

>>> b32encode(b"Ha")
b'MJSQ===='
>>> b32decode(b"MJSQ====")
b'Ha'

>>> b85encode(b"Ha")
b'cYe|'
>>> b85decode(b"cYe|")
b'Ha'

Skráarvinnsla í Python

Stór hluti þeirra gagna sem við vinnum með, hvort sem það er í gagnaglímum eða annars staðar, eru í skráaformi. Innbyggða fallið open(filename, mode) er hannað til að lesa og skrifa skrár og tekur við tveimur færibreytum: annars vegar slóð skráarinnar sem vinna á með og hins vegar hamnum (e. mode) sem opna á skrána í.

Tvíundahamur og textahamur

Hægt er að opna skrár ýmist í tvíunda- eða textaham. Sé skrá opnuð í tvíundaham, skilar open okkur innihaldi skráarinnar sem bætastreng, eða m.ö.o. hráum bætum. Tvíundahamur er táknaður með b.

Sé skrá opnuð í textaham reynir Python að afkóða innihald skráarinnar með þeirri textakóðun sem valin er með encoding færibreytunni. Sé hún ekki sett, notast Python við sjálfgefna kóðun sem stjórnast af stillingum stýrikerfisins. Komi upp villa við afkóðunina, misheppnast kallið í open. Hægt er að hunsa afkóðunarvillur með errors færibreytunni sem hegðar sér eins og samnefnd færibreyta fyrir decode. Textahamur er táknaður með t, en þar sem hann er sjálfgefinn er honum oftast sleppt.

Les og skrif

Hægt er að opna skrár bæði til lesturs, með r (sem stendur fyrir read), og til skrifta, með w (sem stendur fyrir write).

Skrifhamur eyðir gögnum

Þegar skrá sem nú þegar er til er opnuð í skrifham er gömlu skránni eytt og ný (tóm) skrá búin til í staðinn. Python varar okkur ekki við ef þetta gerist. Þetta getur sérstaklega komið í bakið á okkur ef við skrifum forrit sem:

  1. opnar skrá í lesham og sækir gögnin sem hún inniheldur,
  2. opnar sömu skrá í skrifham,
  3. fer yfir gömlu gögnin, breytir og skrifar jafn óðum í skrána.

Ef villa kemur upp í skrefi 3 munum við missa öll upprunalegu gögnin úr skránni.

Hamur Lýsing
"r" eða "rt" Opnar textaskrá til lesturs (sjálfgefinn hamur). Python reynir að lesa gögnin sem kóðaðan texta, ýmist með sjálfgefinni kóðun stýrikerfisins eða þeirri kóðun sem gefin er með encoding. Skilar streng.
"w" eða "wt" Opnar textaskrá til skrifta. Virkar eins og "r" varðandi kóðun texta, en eyðir fyrra innihaldi skráarinnar. Gögn sem skrifuð eru í skrána verða að vera strengir, ekki bætastrengir.
"rb" Opnar skrá til lesturs í tvíundaham. Skilar innihaldi skráarinnar sem bætastreng.
"wb" Opnar skrá til skrifta í tvíundaham. Gögn sem skrifuð eru í skrána verða að vera bætastrengir, ekki strengir.

Hvers vegna rangur hamur skemmir gögn

Ef opnuð er .png myndaskrá í textaham ("w"), og einhver bæti skrifuð í hana, mun Python reyna að breyta ákveðnum bætum (eins og \x0a sem er ný lína) yfir í línulok stýrikerfisins (t.d. \r\x0a á Windows). Þessi eina litla breyting á einu bæti mun gera skrána algjörlega ólesanlega og eyðileggja myndina! Notum ávallt "rb" og "wb" þegar við vinnum með hrá gögn.

Til að lesa gögn upp úr textaskrá er því einfaldlega hægt að gera

f = open('/etc/passwd')
f.read()
þar sem sjálfgefinn hamur er r. Þetta virkar, til dæmis, á fylgiþjóninum þar sem /etc/passwd er kóðuð með UTF-8 sem er sjálfgefin kóðun stýrikerfis fylgiþjónsins (og flestra Linux kerfa).

Til að skrifa í nýja textaskrá getum við prófað

f = open('prufuskra.txt', 'w')
f.write('Fyrri hluti.')
f.write('Seinni hluti.\n')
f.writelines('Lína 2')
f.writelines('Lína 3', 'Lína 4')
Hér sjáum við tvær aðferðir til að skrifa texta í skrá. Annars vegar write, sem skrifar nákvæmlega þann streng sem gefinn er í skrána og hins vegar writelines sem skrifar einn eða fleiri strengi í skrána ásamt því að bæta við nýrri línu í lok hvers strengs til að búa til nýja línu.

Oft er open notað með svokallaðri samhengisstýringu (e. context manager) sem passar upp á að tengingu Python við skrána sé ekki haldið opinni lengur en þörf er á. Prófum að búa til mynd með bláum kassa, í myndasniði sem kallast QOI, með því að opna myndaskrána í tvíundaham.

with open('test.qoi','wb') as f:
    f.write(
        b'qoif\x00\x00\x01,\x00\x00\x01,\x03\x00i' +
        b'\xfd'*1451 +
        b'\xe4' + 
        b'\x00'*7 + 
        b'\x01'
    )
Ef við keyrum þetta á fylgiþjóninum getum við skoðað myndina með chafa test.qoi.

Við getum lesið sömu skrá aftur í tvíundaham með:

with open("test.qoi", "rb") as f:
    # Lesum fyrstu 15 bætin
    haus = f.read(15)
    print("Haus myndar:", haus)

    # Ef við köllum aftur á read heldur Python áfram þar sem frá var horfið.
    afgangur = f.read()
    print("Afgangur:", afgangur)

Hér sjáum við að read tekur valkvæða tölu sem færibreytu sem segir til um hversu mörg bæti, í mesta lagi, eigi að lesa úr skránni. Næsta kall í read heldur þá áfram þar sem frá var horfið.