15. Delo z datotekami#

15.1. Branje datotek#

Delo z datotekami v Pythonu je tako preprosto, da ga že skoraj obvladamo.

Imejmo datoteko z imenom planeti.txt in takšno vsebino:

Merkur
Venera
Zemlja
Mars
Jupiter
Saturn
Uran
Neptun
Pluton

Če jo hočemo brati, jo moramo najprej odpreti.

datoteka = open("planeti.txt")

Kaj se je zgodilo tule? open je, očitno, funkcija, ki kot argument dobi ime datoteke, ki jo hočemo odpreti. Kot rezultat vrne … hm, datoteko? Glede na to, da smo rezultat funkcije priredili spremenljivki datoteka, lahko s print(datoteka) poškilimo vanjo in izvemo naslednje:

print(datoteka)
<_io.TextIOWrapper name='planeti.txt' mode='r' encoding='UTF-8'>

Takole: datoteka je spremenljivka. Ni število (tipa int ali float), tudi ni tipa niz (str) ali seznam (list), temveč neko čudo tipa _io.TextIOWrapper. Pač zelo čudno ime tipa, predstavlja pa neko vrsto datoteke. Nadalje izvemo, da je datoteki, na katero se nanaša spremenljivka z imenom datoteka, ime 'planeti.txt', odprta je na način 'r' (spoiler: to pomeni, da je odprta za branje, read) in da je besedilo shranjeno v formatu UTF-8 (kaj je to, bomo že še izvedeli).

Tole ni bilo nič posebej informativnega. Kaj lahko počnemo z datoteko? Lahko jo prehodimo z zanko for.

for vrstica in datoteka:
    print(vrstica)
Merkur

Venera

Zemlja

Mars

Jupiter

Saturn

Uran

Neptun

Pluton

Če torej podtaknemo datoteko zanki for, jo bo zanka brala vrstico za vrstico.

A gremo še enkrat?

for vrstica in datoteka:
    print(vrstica)

Ne moremo. Ko preberemo datoteko do konca, je prebrana. Če jo hočemo ponovno brati, jo moramo tudi ponovno odpreti. (To ni povsem res, vendar je pri besedilnih datotekah dokaj res.)

Datoteke se v bistvu vedejo kot generatorji, ki smo jih spoznali pred dvema tednoma. next pa te reči.

Zakaj pa so vmes prazne vrstice? V datoteki jih vendar ni. Vse bo jasno, če datoteko preberemo nekoliko drugače. Kot vemo od prejšnjih predavanj, imajo seznami in nizi razne metode, ki z njimi počnejo različne stvari. Prav tako imajo svoje metode datoteke. Ena od njih je read, ki prebere celotno datoteko in vrne njeno vsebino kot niz.

datoteka = open("planeti.txt")
datoteka.read()
'Merkur\nVenera\nZemlja\nMars\nJupiter\nSaturn\nUran\nNeptun\nPluton'

(Mimogrede, metodi read lahko kot argument podamo številko, ki pove, koliko znakov datoteke naj prebere. Če jo pokličemo brez argumentov, pa prebere celo datoteko.)

Vidimo, da datoteka, jasno, vsebuje znake \n, ki povedo, kje se začenja nova vrstica. Ko beremo datoteko z zanko for, dobivamo celotne vrstice, skupaj z \n, torej, recimo ‘Merkur\n’. Če poskusimo izpisati print('Merkur\n'), na koncu niza izpišemo prazno vrstico, še eno pa sam od sebe doda print. Odtod prazne vrstice.

Dva nauka. Prvi: strip je tvoj prijatelj - še prav posebej pri branju datotek.

datoteka = open("planeti.txt")

for vrstica in datoteka:
    print(vrstica.strip())
Merkur
Venera
Zemlja
Mars
Jupiter
Saturn
Uran
Neptun
Pluton

Drugi: kadar po vrsticah berete besedilne datoteke, jih berite z zanko for. Torej tako

for vrstice in datoteka:

in ne tako

for vrstice in datoteka.read().split("\n"):

Drugo ni samo daljše in nepotrebno, temveč lahko tudi ne deluje. Če si že morate brez potrebe zapletati življenje, potem uporabite vsaj

for vrstice in datoteka.read().splitlines()

Poleg metode read ima datoteka še kar nekaj metod, med katerimi za zdaj omenimo readline, ki prebere eno vrstico, in readlines, ki prebere vse vrstice in vrne seznam.

Datoteke vedno beremo po vrsti. Kot pove njihovo ime, datoteke tečejo.

datoteka = open("planeti.txt")
datoteka.readline()
'Merkur\n'
datoteka.readline()
'Venera\n'
datoteka.readline()
'Zemlja\n'
datoteka.readlines()
['Mars\n', 'Jupiter\n', 'Saturn\n', 'Uran\n', 'Neptun\n', 'Pluton']

Najprej smo prebrali tri vrstice in nato ostale. Vračanja nazaj ni. (Je, ampak … pustimo.) In ko smo na koncu, smo na koncu.

datoteka.readline()
''
datoteka.readlines()
[]

Spodobi se, da povemo še ze metodo: close. Z njo datoteko zapremo. Zapiranje je potrebno kadar želimo operacijskemu sistemu (da, ne Pythonu, temveč Windowsom oz. macOs-u oz. Linuxu) povedati, da te datoteke ne beremo več. To je pomembno predvsem zato, ker bi se kak drug program morda odločil v datoteko pisati, vendar mu sistem tega ne bo pustil, če je datoteka odprta.

V Pythonu zapiranje datoteke ni povsem nujno. Če datoteko odpremo v funkciji, se bo kar sama od sebe zaprla, ko bo funkcije konec. Pravijo, da na to ni lepo računati, vendar vsaj sam to pogosto počnem. Datoteko rad odprem kar v zanki for, takole

for vrstice in open("planeti.txt"):

Saj res ni potrebe, da bi jo tlačili v kakšno posebno spremenljivko.

To je pomembno predvsem zato, ker bi se kak drug program morda odločil v datoteko pisati… Pa znamo mi pisati v datoteko?

15.2. Pisanje datotek#

Funkcija open sprejme poleg imena datoteke, ki jo želimo odpreti, še nekaj argumentov. Drugi po vrsti vsebuje način, na katerega želimo odpreti datoteko. Ta je lahko “r”, “w” ali “a”. Način “r” smo že videli zgoraj in vemo, da se nanaša na branje. Potem je očitno, da se “w” na pisanje. Način “a” pa pomeni dodajanje na konec datoteke “a”.

Če torej napišemo

datoteka = open("bogovi.txt", "w")

ustvarimo novo datoteko z imenom bogovi.txt. Če je takšna datoteka že obstajala … tough luck. Zdaj obstaja prazna datoteka s tem imenom. Da uganemo, kako se imenuje metoda za pisanje v datoteko, ni potrebno biti Einstein.

datoteka.write("Hermes")
6

6?! Zakaj je metoda write vrnila 6? Povedala je, koliko znakov je napisala v datoteko. Zakaj? Pojma nimam.

datoteka.write("Afrodita")
8
datoteka.write("Ares")
4
datoteka.write("Zeus")
4
datoteka.write("Kronos")
6

Pri vseh teh vrnjenih številkah je smešno to, da Python v datoteko v resnici (najbrž) še ni napisal ničesar. Če odpremo datoteko, bomo najbrž odkrili, da je prazna. V resnici ne piše vsake drobnarije posebej, temveč čaka, da se bo malo nabralo. Zagotovo pa bo zapisal, vse kar je treba, če datoteko zapremo.

datoteka.close()

Po tem ne moremo več pisati vanjo.

datoteka.write("Caelus")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-20-b7e03db93281> in <module>
----> 1 datoteka.write("Caelus")

ValueError: I/O operation on closed file.

Pač pa lahko datoteko odpremo s PyCharmom ali čim podobnim. In uvidimo, da v njej piše:

HermesAfroditaAresZeusKronosKronos

Ups. Ja, write ni print (hvalabogu). write ne dodaja praznih vrstic.

Popravni izpit:

datoteka = open("bogovi.txt", "w")
datoteka.write("Hermes\n")
datoteka.write("Afrodita\n")
datoteka.write("Ares\n")
datoteka.close()

Če zdaj odpremo datoteko, dobimo, kar smo (najbrž) želeli:

Hermes
Afrodita
Ares

Metoda write se od printa razlikuje še po nečem: medtem ko print dobi poljubno število argumentov poljubnega tipa, sprejme write kot argument en in natančno en niz. Če bi hoteli izpisati prvih deset števil in njihove kvadrate, bi pisali

for i in range(10):
    print(i, i ** 2)
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81

Če bi jih hoteli zapisati v datoteko, bi jih morali najprej preoblikovati v nize.

k = open("kvadrati.txt", "w")
for i in range(10):
    k.write(f"{i} {i ** 2}")
k.close()

15.3. Trenutni direktorij#

Kje mora biti datoteka, da jo bo open našel? Ali, kadar pišemo: kam, v kateri direktorij, jo bo shranil?

Vsak program, ki trenutno teče, ima določen “trenutni direktoij”. V začetku mu ga na nek način določi operacijski sistem (kako, je odvisno od sistema, programa in bogvečesa še). V Pythonu ga izvemo s funkcijo os.getcwd().

import os

os.getcwd()
'/Users/janez/Desktop/predavanja/p1/predavanja'

Kratica “cwd” pomeni “current working directory”. Spremenimo ga z os.chdir():

os.chdir("/Users/jananovak/Desktop")

Če po tem odpremo datoteko v načinu “w”, se bo le-ta pojavila v direktoriju /Users/jananovak/Desktop, kar bi utegnilo biti Janino namizje.

Ime datoteke je lahko relativno ali absolutno.

Relativna imena začnemo brez poševnice. Če je trenutni direktorij Users/janez/Downloads in napišemo open("programi/naloga.py"), bo Python poskusil odpreti datoteko “/Users/jananovak/Desktop/programi/naloga.py”. Takšno ime je torej relativno glede na trenutni direktorij.

Če pa ime datoteke začnemo s poševnico, gre za absolutno ime, od, s kleno slovensko besedo, korenskega imenika oziroma mape. Napišemo lahko torej, na primer, open("/Users/jananovak/Desktop/programi/naloga.py").

Ob poševnicah povejmo še, da dandanes vsi operacijski sistemi sprejemajo običajno, “napredno” poševnico, /. Windowsi (oziroma MS-DOS, na katerem temeljijo) so dolgo uporabljali vzvratno poševnico \, ki pa povzroča same sitnosti - kot smo izvedeli, ko smo govorili o nizih.

15.3.1. Premikanje po direktorijih#

Če želimo ciljni direktorij podati relativno, glede na trenutni direktorij, si lahko pomagamo z naslednjimi triki.

Premik v starševski direktorij:

os.chdir('..')

Premik v bratski direktorij ‘resitve’ (najprej se premaknemo v starsevski dir, nato spustimo v ‘resitve’)

os.chdir('../resitev')

Takole ostanemo v trenutnem direktoriju (uporabno kasneje):

os.chdir('.')

Seveda lahko poljubno kombiniramo zgornje ukaze, tako da vmes damo poševnico (/). Če smo trenutno v /Users/jananovak/Desktop/programi/ in bi radi prišli v /Users/jananovak/Desktop/programi, to naredimo s sledečo relativno potjo:

os.chdir('../../Desktop/programi')

S prvim ../ smo se najprej dvignili v direktorij /Users/jananovak/Desktop/, z drugim v /Users/jananovak/, nato pa smo z Desktop/ vstopili v /Users/jananovak/Desktop/ in nato še z /programi v /Users/jananovak/Desktop/programi.

15.3.2. Kaj je v trenutnem direktoriju?#

Včasih ne poznamo točnega imena datotek v nekem direktoriju, zato nas najprej zanima, kaj sploh je notri. To naredimo z ukazom os.listdir(pot). Takole bi preverili, kaj se skriva v trenutnem direktoriju:

import os
os.listdir('.')
['program.py',
 'podatki.txt']

seveda lahko funkciji podamo poljubno pot in tako preverimo, kaj je v poljubnem direktoriju.

15.4. Kako je zapisano besedilo#

Računalnik v resnici shranjuje samo številke. Da lahko shranimo besedilo, se zmenimo, da vsak znak predstavimo z določeno številko. V ta namen je bilo razvitih več standardov, od katerih je preživel bolj ali manj samo ASCII. Ta pravi, recimo, da veliki A predstavimo s številko 65, B s 66, C z 67… Mali a je 97, b 98 … in tako naprej. Svoje kode imajo tudi drugi znaki: presledek je 32, pika je 46, oklepaj 40… Vsak znak, ki ga lahko natipkamo ali kako drugače napišemo, mora imeti neko kodo. Problem: ASCII je (bil) sedembitni standard, pri čemer so prvih 32 znakov zasedle kontrolne kode, kot recimo prehod v novo vrsto, tabulator, zvonček in še kup eksotičnih reči, ki so jih potrebovali za teleprinterje. Tako so proste le kode od 32 do 127, s čimer lahko zapišemo 96 znakov. To nekako zadošča za angleško abecedo, ločila in še par malenkosti, za trde in mehke č-je pa se Američani, ki so sestavljali standard, niso posebej menili. Kitajci pa si niti s celotnim naborom 96 mest ne bi mogli veliko pomagati.

ASCII je doživel kup razširitev. Že v sedembitni različici smo ga v rajnki domovini popravili v YUSCII, v katerem smo žrtvovali nekaj znakov, kot so zaviti oklepaji in vzvratne poševnice in jih zamenjali s šumniki. Microsoft in IBM sta kasneje sestavila vsak svoj standard, takoimenovane kodne tabele; gre za razporede, v katerih je “spodnji del”, znaki s kodami do 127, enak kot v izvirnem ASCII, v gornjem delu - kode od 128 do 256 - pa so različni drugi znaki. V naših krajih se je najbolj prijela tabela CP1250, namenjeni srednje- in vzhodnoevropskim jezikom, v kateri ima, recimo, č kodo 232. V zahodnoevroski tabeli, CP1252, č-ja ni, na mestu 232 pa je znak è. Poleg teh, Microsoftovih tabel, obstajajo IBMove, kjer je Slovanom namenjen IBM 852; da bi bilo programiranje še preprostejše, obstaja tudi tretji standard, ISO-8859, ki nam je namenil tabelo ISO-8859-2, ki je podobna, vendar ne povsem enaka CP1250.

Ko torej računalnik prikazuje kako besedilo, mora vedeti, v katerem kodnem naboru je zapisan, se pravi, kako razumeti, recimo, številko 232. Če je besedilo v CP1252, je to è, če v CP1250, pa č. In kako naj to ve? V splošnem ne more! Spletne strani navadno vsebujejo glavo, v kateri je zapisan podatek o uporabljenem kodnem naboru. Če tega ni, se bodo šumniki pokazali prav ali pa tudi ne. In še huje: vse besedilo je napisano v istem kodnem naboru, zato se lahko zgodi, da ne bo moglo vsebovati tako črke č kot è. (To morda razloži kaj v zvezi s podnapisi filmov, ki napačno sugerirajo, da so na pikniku jedle èvapèièe s èebulo?)

Obenem pa s tem Kitajci še vedno ne morejo biti zadovoljni, saj jim dodatnih 128 znakov bore malo pomaga.

Takole smo se hecali do pred nekaj leti, ko je prišel v širšo rabo standard Unicode. Ta je narejen takole: vsakemu znaku je prirejena ena koda. Kod je 232, kar je čez in čez dovolj tudi za Kitajce (vsak ima lahko svojo, če hoče!). Nekatere znake je mogoče pisati tudi na več načinov: obstaja, recimo, znak za strešico in č lahko zapišemo kot č (koda 269) ali kot strešico (309) in c (99). Da bi lahko zapisal 232 kod, bi potrebovali po štiri bajte na znak. Američanov to ne bi posebej osrečilo, saj bi se dolžina njihovih besedil početverila in bi se spraševali, kaj je bilo pravzaprav narobe s starim dobrim ASCII. Zato so se snovalci standarda domislili različnih načinov, kako “stisniti” zapis, da bo zahteval manj kot štiri bajte na znak. Med njimi je daleč najbolj razširjen UTF-8, občasno pa vidimo še UTF-16. UTF-8 je sestavljen zelo zvito: če imamo besedilo v starem dobrem ASCIIju (brez neangleških znakov, torej brez kakšnih šumnikov ali francoskih naglasov), se lahko delamo, da je v UTF-8. Čim vsebuje šumnike (recimo po standardu CP1250), pa ni več združljiv z UTF-8 in moramo, če ga hočemo pravilno prebrati, vedeti, po katerem standardu je kodirano.

Smo prišli z dežja pod kap? Smo imeli prej polno kodnih naborov (CP1250 do CP125bogvekoliko, pa IBMovi nabori, pa ISO-8859), zdaj pa imamo kup UTFjev? Niti ne. Razlika je v tem, da so stari nabori določali, kakšne kode imajo posamezni znaki. Po novem imajo vsi znaki določene kode, obstaja le par načinov zapisa teh kod. Med njimi pa vsaj na zahodu prevladuje UTF-8.

Če imamo torej neko besedilo - recimo v neki datoteki, ali pa na neki spletni strani -, ki ga želimo prebrati, bo ponavadi zapisano v UTF-8 ali CP1250. Če je angleško in vsebuje le angleške znake, je to eno in isto. Če gre za slovensko besedilo, pa moramo vedeti, kako je zapisano. Če gre za novejše besedilo, je verjetno v UTF-8, če je starejše kot kakih deset let ali pa prihaja iz vira, s katerim ima kaj opraviti kakšen domači amaterskih izdelovalec spletnih strani, pa CP-1250. CP-1250 se žal še vedno kar pogosto pojavlja tudi v bližini Windowsov. Vedno ga lahko tudi poskusimo najprej prebrati kot UTF-8; če to uspe, je najbrž v resnici UTF-8, sicer ga preberemo kot CP1250.

Kako Pythonu povemo, kako naj bere besedilo? Metoda open ima še par argumentov. Tretji nas ne zanima in naj ima vrednost -1, četrti pa poda vrsto zapisa. Ime mu je encoding. Da preskočimo tretjega, encoding navadno podamo poimensko.

open("planeti.txt", "r", encoding="utf8")
<_io.TextIOWrapper name='planeti.txt' mode='r' encoding='utf8'>

Pa če Pythonu ne povemo, kot kaj želimo brati besedilo - kot kaj ga bo poskusil brati? To je odvisno od nastavitev sistema - zato imajo Windowsi nastavitev “System locale”. Tam sprašuje po “Current language for non-Unicode programs”; če imate nastavljeno slovenščino, bo privzeti kodni nabor CP1250. Na Ubuntuju in Macu je kot privzeto kodiranje nastavljen UTF-8. Kako je s tem pri vas, vam pove funkcija locale.getpreferrencoding().

Kaj pri branju in pisanju datotek pravzaprav naredi ta nastavitev? Tole: ko zapišemo nek niz v datoteko, ga mora Python pretvoriti v zaporedje številk. Ta nastavitev pove, kako - v katero številko naj se spremeni posamezni znak. (Pozoren študent, ki kaj ve, ve tudi, da so tudi nizi v Pythonu, torej spremenljivke tipa str, že shranjene kot zaporedje številke. Drži, vendar Python interno shranjuje nize pač v nekem standardu in nič nas ne briga, v katerem (v resnici v UTF-16 ali UTF-32, saj bi bil UTF-8 za tole zelo nepraktičen), pri zapisovanju v datoteko pa pretvori v tak standard, kot želimo. Pri branju pa je ravno obratno: ko Python bere datoteko, mora spreminjati številke v znake niza; ta nastavitev pove, kako.

Tule bi se lahko pogovorili še marsikaj. Predvsem ne vsebujejo vse datoteke besedil. Vendar so te stvare že precej tehnične in nekatere tudi specifične za Python, tako da se tu morda ustavimo. Več pa v dodatnih zapiskih

15.5. Branje in pisanje JSON datotek#

Datoteke se uporabne, saj lahko z njimi delimo podatke med računalniki. Podatke, pišemo in beremo vrstico za vrstico, kar je naraven način za branje in pisanje stvari, ki jih v pythonu hranimo v podatkovni strukturi seznam. Vendar pa včasih takšno preprosto pisanje ne zadošča. Kako bi shranili kaj bolj kompleksnega, kot na primer slovar?

Recimo, da bi radi shranili naslednji slovar:

glavna_mesta = {
    'Evropa': {
        'Slovenija': 'Ljubljana',
        'Hrvaška': 'Zagreb',
        'Avstrija': 'Dunaj',
        'Madžarska': 'Budimpešta',
        'Italija': 'Rim'
    },
    'Afrika': {
        'Egipt': "Kairo"
    }
}

Če bi ga shranili v datoteko, kot smo vajeni do sedaj, bi ali izgubili večnivojsko strukturo, ali pa poskrbeli za nek nestandarden način shranjevanja, ki ga drugi programerji ne bi razumeli.

Zato raje shranimo v formatu json, ki je narejen točno za shranjevanje in deljenje slovarjev.

Zgornji slovar tako shranimo v datoteko glavna_mesta.json, ki ima sledečo vsebino:

{
    "Evropa": {
        "Slovenija": "Ljubljana",
        "Hrvaška": "Zagreb",
        "Avstrija": "Dunaj",
        "Madžarska": "Budimpešta",
        "Italija": "Rim"
    },
    "Afrika": {
        "Egipt": "Kairo"
    }
}

Vidimo, da je struktura datoteke v formatu json skoraj identična zapisu slovarja v Pythonu. Glavna razlika je, da json očitno pozna zgolj dvojni narekovaj (").

Seveda se z lepim shranjevanjem v datoteko ne bomo ukvarjali sami. Za to bo poskrbela knjižjica json.

Slovar v datoteko shranimo s funkcijo json.dump:

import json

with open('izhod.json', 'w') as jsonfile:
    json.dump(glavna_mesta, jsonfile, ensure_ascii=False)

Najprej moramo odpreti neko datoteko za pisanje, nato pa funkciji podamo spremenljivko s slovarjem (glavna_mesta) in spremenljivko z odprto datoteko (jsonfile).

Ker smo datoteko odprli v kontekstu, se po uspešnem izvozu zapre sama.

V funkcijo smo dodali še en parameter. ensure_ascii=False, poskrbi, da se bodo čšž-ji shranili, kot smo vajeni. To seveda lahko izpustimo, če delamo s standardno (angleško) ASCII kodno tabelo.

Če odpremo nastalo datoteko izhod.json, opazimo, da je notri res naš slovar:

{"Evropa": {"Slovenija": "Ljubljana", "Hrvaška": "Zagreb", "Avstrija": "Dunaj", "Madžarska": "Budimpešta", "Italija": "Rim"}, "Afrika": {"Egipt": "Kairo"}}

Edino, kar nas moti je, da je celoten slovar stisnjen v eno vrstico. Takšen zapis je še vedno veljaven, razumela ga bo vsaka knjižnjica, ki bo poskusila odkodirati json datoteko, edino ni najbolj berljiv. To lahko popravimo z dodatkom parametra indent=4, ki poskrbi, da bo vsak nov nivo zamaknjen za 4 presledke bolj v desno:

with open('izhod.json', 'w') as jsonfile:
    json.dump(glavna_mesta, jsonfile, ensure_ascii=False, indent=4)

Datoteka izhod.json sedaj izgleda veliko lepše:

{
    "Evropa": {
        "Slovenija": "Ljubljana",
        "Hrvaška": "Zagreb",
        "Avstrija": "Dunaj",
        "Madžarska": "Budimpešta",
        "Italija": "Rim"
    },
    "Afrika": {
        "Egipt": "Kairo"
    }
}

Iz datoteke beremo s funkcijo json.load:

with open('izhod.json', 'r') as jsonfile:
    print(json.load(jsonfile))
{'Evropa': {'Slovenija': 'Ljubljana', 'Hrvaška': 'Zagreb', 'Avstrija': 'Dunaj', 'Madžarska': 'Budimpešta', 'Italija': 'Rim'}, 'Afrika': {'Egipt': 'Kairo'}}

Spet odpremo datoteko, tokrat za branje, in jo podamo funkciji json.load, ki vrne prebrani slovar.

Mimogrede, shranjevanju podatkovne strukture, ki “živi” med izvajanjem programa v delovnem pomnilniku računalnika, v datoteko, rečemo serializacija, obratni operaciji (nalaganju iz datoteke v pomnilnik) pa deserializacija.

Včasih pa ne želimo pisati direktno v datoteko, pač pa bi radi vsebino slovarja samo pretvorili v niz, ki ga potem lahko shranimo na kakšen drugačen način, npr. v podatkovno bazo, ki je vajena shranjevanja nizov. Za takšne primere ima knjižnjica json na voljo funkciji json.dumps in json.loads (s se nanaša na string oz. niz). Tako lahko slovar pretvorimo v niz:

json_str = json.dumps(glavna_mesta, ensure_ascii=False, indent=4)
print(json_str)
{
    "Evropa": {
        "Slovenija": "Ljubljana",
        "Hrvaška": "Zagreb",
        "Avstrija": "Dunaj",
        "Madžarska": "Budimpešta",
        "Italija": "Rim"
    },
    "Afrika": {
        "Egipt": "Kairo"
    }
}

Ker smo spet uporabili parameter indent=4 smo dobili lep izpis z zamiki. Tega sicer ponavadi ne delamo, ko shranjujemo v bazo, je pa uporabno, ko želimo s funkcijo print prikazati vsebino slovarja na zaslonu.

Funkcija json.dumps pa tako naše podatke pretvori nazaj iz niza v slovar:

slovar_2 = json.loads(json_str)
print(slovar_2)
{'Evropa': {'Slovenija': 'Ljubljana', 'Hrvaška': 'Zagreb', 'Avstrija': 'Dunaj', 'Madžarska': 'Budimpešta', 'Italija': 'Rim'}, 'Afrika': {'Egipt': 'Kairo'}}